DirectX-Anwendungen mit C++ erstellen - Einführung in DirectDraw
DirectX ist eine Schnittstelle die Microsoft eingeführt hat um es den Programmieren zu
ermöglichen hardwareunabhängige Software zu schreiben. Viele denken , wenn Sie DirectX höhren
sofort an schnelle 3D-Computer-Spiele im Vollbildmodus - aber DirectX ist mehr.
Die einzelnen Komponenten werden wir hier Schritt für Schritt kennen lernen. Von DirectDraw über
Direct3D zu DirectSound. Daneben existieren noch DirectInput, DirectPlay und DirectMusic die wir erst
eimal nicht planen zu behandeln.
DirectX hält sich bei seinem Aufbau an den von Microsoft entwickelten COM-Standard. COM steht für
Commen Object Modell. Mehr Infos über dieses Modell gibt es auf den Microsoft-Seiten.
In der C/C++-Umgebung werden uns über die DirectX-Header-Dateien Schnittstellen(Interfaces) definiert, die es uns
ermöglichen auf diese COM-Objekte zuzugreifen.
Um jedoch DirectX-Anwendungen programmieren und testen zu können sind mehrere Vorrausetzungen zu erfüllen :
- Betriebssystem Win9x/NT
- DirectX-Treiber müssen installiert werden (Version 7 ist für dieses Tutorial erforderlich)
- Header- und Lib-Files der aktuellen DirectX-Version (hier runterladen)
- Die Library-Files für den Borland-Compiler befinden sich innerhalb des DX-SDK in einem seperaten Unterverzeichnis
- Lcc-Win32 Benutzer können weder die MS-Libs noch die Borland-Libs verwenden. Passende Libs hier runterladen
- NT-Benutzer benötigen das Service Pack 6/6a um die Funktionalität von DirectX 7 nutzen zu können
Unser erstes DirectX-Programm
Ein wohl guter Einstig in die DirectX-Programmierung ist eine "graphische" Anwendung. Denn hier sieht man was
man Programmiert hat.
Unser Ziel wird es sein, in einem Fenster eine DirectDraw-Oberfläche zu erstellen in die wir dann
beliebige Punkte, Figuren und sonstiges zeichnen können.
Unser Programm soll TestDDraw heissen. Legen Sie sich dazu mit VC++ bzw. lcc-Win32 ein neues Projekt an. Achten Sie darauf, dass es
sich um ein Projekt handelt, dass eine Windows-Anwendung erstellt (keine Konsolenanwendung). Fügen Sie, je nach Entwicklungsumgebung, die Dateien
ddraw.lib und dxguid.lib zu den zu linkenden Objekt-files hinzu.
Lcc-Win32-Benutzer benötigen die dxguid.lib nicht.
Da der Compiler von lcc-Win32 ein C-Compiler und kein C++-Compiler ist gehen wir kurz auf die Unterschiede
in der Programmierung ein:
DirectX unterstützt sowohl den Zugriff über C als auch C++ Programme. Der grundlegende Unterschied liegt in der
Verwaltung der Komponenten der DirectX-COM-Objekte.
lpDD sei ein Zeiger auf die Schnittstelle zu einem solchen Objekt. Dann werden die Memberfunktionen über lpDD->Memberfunktion(...)
aufgerufen. Aber nur in C++. In C hingegen wird der Zugriff über eine virtuelle Tabelle gelöst. Das bedeutet, dass
hier lpDD->lpVtbl->Memberfunktion(lpDD, .....) auf die gefragte Funktion zu greift. lpVtbl ist der Zeiger auf
die virtuelle Tabelle der Objekte. Dabei erhält die Funktion an erster Stelle einen zusätzlichen Übergabeparameter, welcher das Objekt,
von dem die Funktion aufgerufen werden soll, innerhalb der virtuellen Tabelle referenziert.
Die Quellcodes sind alle in C gehalten, da sie ohne Änderungen sowohl mit VC++ als auch mit lcc-Win32 compiliert werden
können.
Wir legen in unserem Projekt eine Datei TestDDraw.c an und können sofort loslegen.
Als erstes müssen wir neben dem Windowsheader den Header von DirectDraw einbinden.
/*
* TestDDraw.c
*/
#include
#define INITGUID // <- nur für lcc-win32 Compiler
#include "ddraw.h"
Nach der DirectX-Architektur wird immer ein "Master"-Objekt angelegt, von dem dann alle anderen abgeleitet werden können.
In unserem Beispiel (einer DirectDraw-Anwendung) bildet ein DirectDraw-Objekt diese Zentrale.
Von diesem kann dann z.Bsp. eine Zeichenfläche, in die wir "malen" wollen, abgeleitet werden. Ein solches Objekt
wird als Surface bezeichnet. Dummerweise haben die DirectX-Entwickler die eigentliche Zeichenfläche und ihre
Eigentschaften bzw. Beschreibung von einander getrennt. So wird die Beschreibung eines Surfaces in einer Struktur Namens
DDSURFACEDESC abgelegt.
Zur Darstellung von Grafiken mit DirectX benötigen wir zwei dieser Zeichenflächen. Eine repräsentiert unseren Bildschirm, die andere
unsere Arbeitsfläche in der wir wirklich "malen".
LPDIRECTDRAW7 lpDDraw7 = NULL;//Zeiger a. die Schnittstelle des DD-Obj. der Version 7
LPDIRECTDRAWSURFACE7 lpDSurf7 = NULL;//unser Bildschirmsurface
DDSURFACEDESC2 DDSurfDesc2; //die Beschreibung unserer Bildschirmzeichenfläche
LPDIRECTDRAWSURFACE7 lpDPaint7 = NULL;//die Arbeitsfläche
DDSURFACEDESC2 DDPaintDesc2; //und deren Beschreibung
BOOL IsDDraw = FALSE;//ist TRUE wenn DirectDraw initialisiert wurde
Da eine Windows-Anmwendung wie bekannt von einem Window aus gesteuert wird, ist die WinMain-Funktion
ganz im API-Stil gehalten. Die Messagebehandlungsroutine ist hier nicht abgebildet.
ACHTUNG: MFC und DirectX arbeiten nicht fehlerfrei zusammen
Die Funktion WinMain definiert unser Anwendungsfenster und zeigt es :
BOOL APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASS wc;
HWND hwnd;
MSG msg;
wc.lpszClassName = "TestDDraw";
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND + 1);
wc.lpszMenuName = NULL;
RegisterClass(&wc);
hwnd = CreateWindow ("TestDDraw", "Test DirectDraw Fenster", WS_OVERLAPPED,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow (hwnd, SW_SHOW);
IsDDraw = InitDirectDraw(hwnd); //DirectDraw initailisieren
UpdateWindow(hwnd); //erzwingen einer WM_PAINT-Message
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitDirectDraw(); //DirectDraw wieder abbauen
return msg.wParam;
}
Der nächste Schritt wird die Initialisierung des DirectDraw-"Modus" sein. Dieser Schritt
wird in einer seperaten Funktion Names InitDirectDraw abgehandelt die TRUE zurückliefert,
wenn DirectDraw erfolgrich initialisiert werden konnte.
Aufrufe von DirectDraw-Methoden geben DD_OK zurück, wenn dabei kein Fehler aufgetreten ist.
Das Erzeugen des zentralen DirectDraw-Interfaces läuft in folgenden Schritten ab:
- Anlegen eines "Ur"-DirectDraw-Objektes (das der DX-Version 1)
- Dieses nach einer Schnittstelle zu einem Objekt höherer Version befragen
- Das "Ur"-DirectDraw-Objekt wieder löschen
- Mit dem DirectDraw-Interface der höheren Version kann nun "gearbeitet" werden
Der folgende Code zeigt die oben geschilderten Schritte:
BOOL InitDirectDraw(HWND hwnd)
{
LPDIRECTDRAW lpDDraw = NULL;
// anlegen des Objektes: lpDDraw zeigt danach auf die Schnittstelle zu diesem Objekt
if (DirectDrawCreate(NULL, &lpDDraw, NULL)!= DD_OK)
{
MessageBox(hwnd, "Konnte DirectDraw-Objekt nicht erzeugen.", NULL, MB_OK);
return FALSE;
}
//das "Ur"-DD-Objekt nach einer Schnittstelle zu einem DD-Objekt der Version 7 fragen
//wenn diese Schnittstelle existiert, dann wird ein Objekt der Version 7 angelegt
//und ein Zeiger auf die Schnittstelle zu diesem Objekt in lpDDraw7 gespeichert
if (lpDDraw->lpVtbl->QueryInterface(lpDDraw, &IID_IDirectDraw7, (void **)&lpDDraw7)!=DD_OK)
{
MessageBox(hwnd, "Keine Schnittstelle zu DirectDraw7 vorhanden", NULL, MB_OK);
return FALSE;
}
//das "Ur"-Objekt wieder freigeben, da wir es nicht länger brauchen
lpDDraw->lpVtbl->Release(lpDDraw);
Nun kommt auch schon die Entscheidung ob die Anwendung im Vollbildmodus laufen soll oder nicht.
Wir wollen diese Frage als erstes verneinen. Aber die Tatsache Vollbild- oder Fenstermodus legt die Member-
Funktion SetCooperativeLevel des DirectDraw-Objektes fest und verknüpft dieses Objekt irgendwie mit
dem Fenster unserer Anwendung.
if (lpDDraw7->lpVtbl->SetCooperativeLevel(lpDDraw7, hwnd, DDSCL_NORMAL) != DD_OK)
{
MessageBox(hwnd, "Konnte Kooperationslevel nicht setzen", NULL, MB_OK);
return FALSE;
}
Nun haben wir den ersten grossen Schritt getan - doch sehen werden wir davon noch nichts.
Es gilt nun eine Zeichenfläche anzulegen in die wir dann bunte Punkte "malen" können. Wir erinnern uns -
Zeichenfläche war ein Surface und alle anderen Objekte neben dem Zentralen DirectDraw-Objekt müssen
von diesem "abgeleitet" sein. Also benutzen wir zum erzeugen unserer Zeichenfläche die CreateSurface-Funktion
des DirectDraw-Objektes. Dieser Funktion müssen wir eine Beschreibung der Zeichenfläche in Form eines zeigers auf eine
DDSURFACEDESC-Struktur übergeben. Sie muss als erstes komplett mit 0 befüllt werden und muss nach DirectX-Festlegung
in der Struktur-Variablen dwSize die Grösse der Struktur enthalten.
Merke: Bei DirectX muss in allen Strukturen vor der Übergabe an eine Funktionen mindesten das Feld dwSize mit der entsprechenden Strukturgrösse belegt worden sein.
//zum Anlegen einer Zeichenfläche die Beschreibung erstmal mit 0 initialisieren
memset(&DDSurfDesc2, 0, sizeof(DDSURFACEDESC2));
//DirectX erwartet in solchen Strukturen grundsätzlich eine Grössenangabe der Struktur
DDSurfDesc2.dwSize = sizeof(DDSURFACEDESC2);
//dieses Flag steuert welche Daten für die Anlage der Zeichenfläche genutzt werden sollen
DDSurfDesc2.dwFlags = DDSD_CAPS;
//Struktur ddsCaps füllen
DDSurfDesc2.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |//primäre Zeichenfläche (sichtbar)
DDSCAPS_VIDEOMEMORY;//Surface im Grafikkartenspeicher anlegen
DDSurfDesc2.ddsCaps.dwCaps2 = 0;
//erzeugen der Zeichenfläche, lpDSurf ist dann der Zeiger auf die Schnittstelle
if (lpDDraw7->lpVtbl->CreateSurface(lpDDraw7, &DDSurfDesc2, &lpDSurf7, NULL) != DD_OK)
{
MessageBox(hwnd, "Konnte primäre Zeichenfläche nicht anlegen", NULL, MB_OK);
return FALSE;
}
//es folgt unsere "Arbeitsfläche"
memset(&DDPaintDesc2, 0, sizeof(DDSURFACEDESC2));
DDPaintDesc2.dwSize = sizeof(DDSURFACEDESC2);
DDPaintDesc2.dwFlags = DDSD_CAPS | DDSCAPS_HEIGHT | DDSCAPS_WIDTH;
DDPaintDesc2.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |//normale Zeichenfl.("unsichtbar")
DDSCAPS_SYSTEMMEMORY;//Surface im Hauptspeicher anlegen
DDPaintDesc2.ddsCaps.dwCaps2 = 0;
DDPaintDesc2.dwHeight = 200; //die Zeichenfläche ist 200 Bildpunkte hoch
DDPaintDesc2.dwWidth = 200; //und 200 Bildpunkte breit
if (lpDDraw7->lpVtbl->CreateSurface(lpDDraw7, &DDPaintDesc2, &lpDPaint7, NULL) != DD_OK)
{
MessageBox(hwnd, "Konnte normale Zeichenfläche nicht anlegen", NULL, MB_OK);
return FALSE;
}
return TRUE;
}
Nun sind wir so weit, dass wir sagen können -> DirectDraw erfolgreich initialisiert. Wir können nun
in unser Surface zeichnen. Wie das geht ?? Noch ein wenig Gedult, denn wer so schön DirectX initialsiert hat, der sollte
es auch wieder sauber aufräumen !! Das ist an sich nicht viel Aufwand und findet in der Funktion ExitDirectDraw statt, die
weder Übergabeparameter erhält noch einen Wert zurückgibt.
Es werden hier nun alle Instanzen von DirectDraw-Objekten wieder freigegeben. Dazu überlegen wir zu erst, welche Instanzen nach der
Initialisierung im Speicher vorhanden sind.
Das Ergebnis: Unsere beiden Zeichenflächen und das zentrale DirectDraw-Objekt.
Für einige bestimmt klar - aber dennoch eine Überlegung wert - in welcher Reihenfolge die Objekte abgebaut werden müssen. Und hier ganz klar
zuerst die Objekte die zuletzt angelegt wurden und ganz am Ende das zentrale DirectDraw-Objekt. Freigegeben werden die Objekte über die
Schnittstellenfunktion Release.
void ExitDirectDraw(void)
{
lpDPaint7->lpVtbl->Release(lpDPaint7);
lpDSurf7->lpVtbl->Release(lpDSurf7);
lpDDraw7->lpVtbl->Release(lpDDraw7);
}
Wenn wir uns nun die Funtion InitDirectDraw noch einmal genauer ansehen, dann werden wir feststellen, dass nicht in jedem
Fall alle Objekte existieren. Wenn die Initialisierung Fehlgeschlagen ist, kann zum einen nur das zentrale DirectDraw-
Objekt existieren oder dieses und nur eine der beiden Zeichenflächen. Aus diesem Grunde ist es sinnvoll diese Fälle
abzufangen, in dem man fragt, ob die Zeiger auf das Interface vielleicht ins Nirvana (NULL)
zeigen, denn in diesem Falle wurde das Objekt nicht angelegt und ein Release würde zu einem Programmfehler führen.
Also ist es besser, wenn ExitDirectDraw so aussieht:
void ExitDirectDraw(void)
{
if (lpDPaint7 != NULL) lpDSurf7->lpVtbl->Release(lpDPaint7);
if (lpDSurf7 != NULL) lpDSurf7->lpVtbl->Release(lpDSurf7);
if (lpDDraw7 != NULL) lpDDraw7->lpVtbl->Release(lpDDraw7);
}
Nun haben wir denke ich ein gutes Fundament geschaffen um zu der eigentlichen Darstellung eines Bildpunktes kommen zu können. In DirectX sind
die Bilddaten irgendwo in einem Surface abgelegt. Um diese nun manipulieren zu können, wäre ein Zeiger auf diese Daten wohl eine gute Lösung.
Genau diese Zeiger wird beim Aufruf der Funktion Lock einer Zeichenfläche in die Variable lpSurface der DDSURFACEDESC - Struktur geschrieben.
Dieser Zeiger ist jedoch vom Typ VOID - was bei der Manipulation des Bildspeichers beachtet werden sollte.
Der Aufruf dieser Funktion hat allerdings einen kleinen Haken: Er sperrt das gesamte GDI bis zum Aufruf des Gegenstückes UnLock.
Will heissen - zwischen diesen beiden Funktionsaufrufen niemals einen Breakpoint setzen und auch keine MessageBox'en anzeigen. Dies kann unter Umständen zu
einem klitze kleinen Systemabsturz führen.
Die nächste Funktion die wir codieren soll also das PaintSurface sperren , einige Manipulationen an den Bilddaten vornehmen und es dann wieder entsperren.
Wir nennen diese Funktion einfach ManipulatePaintSurface. Sie wird innerhalb von WinMain direkt nach InitDirectDraw aufgerufen, wenn
IsDDraw den Wert TRUE hat.
void ManipulatePaintSurface(void)
{
BYTE *lpByteScreen;
int i;
if (lpDPaint7->lpVtbl->Lock(lpDPaint7, NULL, &DDPaintDesc2, DDLOCK_WAIT, NULL) == DD_OK)
{
//hier niemals einen Breakpoint platzieren oder mit dem Debugger hinein springen
//einen byte-Pointer auf die Adresse der Bilddaten setzen
lpByteScreen = DDPaintDesc2.lpSurface;
//die ersten 50 Byte im Screen mit 255 belegen:
for (i=0;i<50;i++)
{
*lpByteScreen = 255;
lpByteScreen++;
}
//Surface entsperren
lpDPaint7->lpVtbl->Unlock(lpDPaint7, NULL);
}
else MessageBox(NULL, "Konnte Zeichenfläche nicht sperren", NULL, MB_OK);
}
Nun haben wir zwar in unserer Zeichenfläche irgendwelche Bildpunkte manipuliert, werden aber auf dem Monitor
nichts der gleichen sehen. Wir haben da ja noch die zweite Zeichenfläche die ja den Bildschirm "repräsentiert".
Es gilt also die geänderte Zeichenfläche irgendwie auf den Bildschirm bzw. in unser Anwendungsfenster zu "zaubern". Das ganze soll immer dann
geschehen, wenn das Anwendungsfenster dargestellt wird. Also während der WM_PAINT-Message.
Zum bessere Verständnis hier einmal die Ursprüngliche Messagebehandlungsroutine als Quelltext:
LRESULT WINAPI WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
switch (msg)
{
case WM_PAINT:
//das sollte ja bekannt sein
BeginPaint(hwnd, &ps);
//hier wird dann die DX-Zeichenfläche geswitcht
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
return TRUE;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return FALSE;
}
Wie oben angedeutet wird sich die Darstellung der Zeichenfläche zwischen BeginPaint und EndPaint
abspielen. Diese beiden Befehle - die immer dieses Paar bilden - sorgen dafür, dass die WM_PAINT-Message vom
Messagequeue entfernt wird. Normalerweise wird die WM_PAINT-Message ununterbrochen an ein Fenster gesendet, aber wenn es
weder die Position, noch die Grösse ändert braucht es ja nicht neu gezeichnet werden.
Wie funktioniert nun aber dieser Switch der Zeichenflächen ?
Die Antwort ist recht einfach. Die Memberfunktion Blt der Zielzeichenfläche vermag es die Bilddaten
einer beliebiegen Quellzeichenfläche auf sich selbst zu kopieren (auch blitten genannt).
Dabei wird jeweils ein rechteckiger Ausschnitt der Quelle in einen rechteckigen Ausschnitt des Zieles kopiert.
In WindowProc werden zu diesem Zweck zwei Rechtecke vom Typ RECT definiert. Die Quelle sei SRect (SourceRect) und das Ziel sei
DRect (DestinationRect).
Da nun die Zielzeichenfläche den gesammten Bildschirm repräsentiert, ist deren Koordinatensystem auch so ausgelegt. Das heisst, dass
ein Rechteck mit den Punkten ((0,0),(200,200)) oben links auf dem Bildschirm wäre und nicht oben links in unserem Fenster. Da kommt uns die GDI-Funktion
MapWindowPoints gerade recht. Sie vermag es Koordinaten die sich auf ein Fenster beziehen auf Koordinaten die sich auf den gesammten Bildschirm
beziehen umzurechnen.
Das Anzeigen der Arbeitszeichenfläche auf dem Bildschirm sieht im ganzen dann wie folgt aus:
//nur wenn DirectDraw erfolgreich initialisiert wurde
if (IsDDraw == TRUE)
{
//die Quelle soll ja die gesammte Arbeits-Zeichenfläche sein
//diese haben wir mit einer Höhe und Breite von 200 erstellt!!
SRect.top = 0;
SRect.left = 0;
SRect.right = 199;
SRect.bottom = 199;
//das Zielrechteck soll die selben Masse haben.
//dadurch kommt es zu keiner Verzerrung.
DRect = SRect;
//die DRect-Koordinaten beziehen sich auf unser Anwendungsfenster
//sie werden auf Bildschirmkoordinaten umgerechnet
//der letzte Parameter 2 gibt an wieviele Punkte in DRect zu ändern sind
MapWindowPoints(hwnd, NULL, (LPPOINT)&DRect, 2);
//Der Blitter kopiert die Bilddaten
//das Flag DDBLT_WAIT gibt an, dass dieser Vorgang wartet bis der Blitter
//frei ist um ihn auszuführen
lpDSurf7->lpVtbl->Blt(lpDSurf7, &DRect, lpDPaint7, &SRect, DDBLT_WAIT, NULL);
}
Einer kurzen Erläuterung bedarf mit sicherheit das Flag DDBLT_WAIT. Das kopieren der Bilddaten
wird vom sogenannten Blitter übernommen. Dieser Blitter kopiert aber auch die Bilddaten aller anderen
Anwendungen hinund her. Das kann dazu führen, dass er zu dem Zeitpunkt zu dem wir die Zeichenfläche blitten
wollen, gerade beschäftigt ist. Nun würde der Blt-Befehl mit der Fehlermeldung, dass der Blitter gerade
"belegt" ist seine Artbeit verweigern. Das hilft uns aber nicht weiter, da wir unbedingt die Bilddaten kopieren wollen.
An dieser Stelle kommt dieses Flag zum Zuge, was dem Blt-Befehl sagt, dass er warten soll bis der Blitter frei ist und dann
die Zeichenflächen kopieren soll.
An dieser Stelle sind wir nun soweit, dass wir das Projekt compilieren, linken und testen können.
Für alle diejenigen, die den Quellcode nicht hier abtippen möchten, gibts den Quelltext (zip-File) in elektronischer Form
Nachdem das Projekt erstellt wurde, können wir
das Programm starten. Unser Anwendungsfenster
sollte dann wie hier rechts dargestellt aussehen.
Oben links im Fenster sollte sich ein dünner
wiesser Strich befinden.
|
|
Nachdem nun sichergestellt ist, dass wir die Manipulationen die wir in der Arbeitszeichenfläche vornehmen
auch auf unserem Bildschirm erscheinen, werden wir uns etwas intensiver mit der manipualtionsfunktion ManipulatePaintSurface
auseinandersetzen.
In jedem SChleifendurchlauf erhöhen wir den Zeiger auf unsere Bilddaten um ein Byte und erhalten somit eine waagerechte Linie. Eine "gute"
Manipulation lässt aber auch die Darstellung einer Senkrechten Linie zu.
Vom theoretischen Ansatz ist das eigentlich ganz leicht: Da im Speicher alle Zeilen der Bilddaten direkt hinter einander liegen
bräuchte man den Zeiger nur immer weiter um ein Byte erhöhen bis man irgendwann in der nächsten Zeile landet. Da es
recht schwierig zu ermitteln ist wie viele Bytes eine Zeile hat, stellt uns diese Information die Funktion Lock in der Variablen lPitch der
DDSURFACEDESC-Struktur zur Verfügung.
Folgende Änderung der Funktion ManipulatePaintSurface hat eine senkrechte Linie zur Folge.
void ManipulatePaintSurface(void)
{
BYTE *lpByteScreen;
int i;
if (lpDPaint7->lpVtbl->Lock(lpDPaint7, NULL, &DDPaintDesc2, DDLOCK_WAIT, NULL)==DD_OK)
{
//hier niemals einen Breakpoint platzieren oder mit dem Debugger hinein springen
//einen byte-Pointer auf die Adresse der Bilddaten setzen
lpByteScreen = DDPaintDesc2.lpSurface;
//in den ersten 50 Zeilen jeweils das erste Byte mit 255 belegen
for (i=0;i<50;i++)
{
*lpByteScreen = 255;
//Zeiger um die Länge einer Zeile in bytes erhöhen
lpByteScreen += DDPaintDesc2.lPitch;
}
//Surface entsperren
lpDPaint7->lpVtbl->Unlock(lpDPaint7, NULL);
}
else MessageBox(NULL, "Konnte Zeichenfläche nicht sperren", NULL, MB_OK);
}
Das Ergebnis dieser Änderung sehen wir rechts.
Erstaunlicherweise ist die Linie nun nicht mehr weiss
sondern blau !
|
|
Wenn man nun diese beiden Methoden der Manipultion verknüpft kann man recht einfach die senkrechte Linie
weiter in die Mitte rücken oder die waagerechte Linie mehr nach unten zeichnen. Sogar schräge Linien sind
möglich.
An dieser Stelle wünschen wir viel Freude beim experimentieren.
Weiter geht es dann im zweiten Teil.
|
|
|