www.codeworx.org/directx_tuts/Einführung in DirectDraw: Teil 6, Clipping

Einführung in DirectDraw: Teil 6, Clipping
Datum: 3.5.00
Autor: Tim-Oliver Husser

Themen für Teil 6 dieses Tutorials:
Was ist Clipping ?
Und wie gehts ?
Umstruktierter Code

Was ist Clipping ?
Erinnern Sie sich noch an diese Zeilen aus dem letzten Tutorial ?

   SetRect(&rectbitmap, 220, 165, 420, 315);
   ...
   lpddsBack->Blt(&rectbitmap, lpddsBitmap, NULL, DDBLT_WAIT, NULL);

Haben Sie schon einmal ausprobiert, was passiert, wenn Sie das Bild so auf den Back Buffer blitten, daß es nicht ganz draufpaßt ? Etwa mit dieser SetRect-Anweisung ?

   SetRect(&rectbitmap, -50, 0, 150, 150);

Nein ? Versuchen Sie es mal ! Und, was passiert ? Ja genau: Nichts !!
Jetzt kommt natürlich die Frage, warum nichts kommt. Ganz einfach: DirectDraw erwartet, daß alles, was man blittet, auch vollständig auf die Ziel-Surface draufpaßt!
Eine Möglichkeit, daß Problem zu lösen wäre jetzt, das zu blittende Bitmap auf der linken Seite um 50 Pixel abzuschneiden. Steht das Bitmap still ist das ja kein Problem, aber wenn es animiert (bewegt) werden soll, ist es ziemlich umständlich, bei jedem Durchlauf einen anderen Teil wegzuschnippeln.
Aber es geht auch einfacher! Für diesen Fall gibt es in DirectDraw die sogenannten Clipper. Diese sind eigentlich dazu gedacht, im Fenstermodus den Bereich festzulegen, auf den DirectDraw zeichnen darf. Wenn man einen Clipper aber im Vollbildmodus über den ganzen Bildschirm legt, nimmt er einem das lästige "abschneiden" ab.

Und wie gehts ?
Einen Clipper zu erstellen ist eigentlich ganz einfach. Zuerst brauchen wir eine Variable, die das Objekt aufnehmen kann:

   LPDIRECTDRAWCLIPPER lpddClipper;

Jetzt gehts los! Der nun folgende Code kommt wie bisher in allen Tutorials vor die Nachrichtenschleife, also vor die Zeile:

   running=true;

Zuerst erstellen wir das Clipper-Objekt. Dies geschieht mit der CreateClipper-Methode des DirectDraw-Objektes:

   ddrval = lpDirectDraw->CreateClipper(0, &lpddClipper, NULL);
   if (ddrval != DD_OK)
   {
      lpddsBack->Release();
      lpddsBack = NULL;
      lpddsPrimary->Release();
      lpddsPrimary = NULL;
      lpDirectDraw->Release();
      lpDirectDraw = NULL;
      return false;
   }

Jetzt kreieren wir eine neue Struktur, erstellen gleich eine Variable dieses Typs mit dem Namen cliplist und füllen diese mit den Information über den Clipper, also in erster Linie der Größe:

   struct
   {
      RGNDATAHEADER rdh;
      RECT rgndata[1];
   } cliplist;
   cliplist.rdh.dwSize = sizeof (RGNDATAHEADER);
   cliplist.rdh.iType = RDH_RECTANGLES;
   cliplist.rdh.nCount = 1;
   cliplist.rdh.nRgnSize = 0;
   cliplist.rdh.rcBound.left = 0;
   cliplist.rdh.rcBound.top = 0;
   cliplist.rdh.rcBound.right = 640;
   cliplist.rdh.rcBound.bottom = 480;
   cliplist.rgndata[0].left = 0;
   cliplist.rgndata[0].top = 0;
   cliplist.rgndata[0].right = 640;
   cliplist.rgndata[0].bottom = 480;

Die einzelnen Felder des Datentyps sollten selbsterklärend sein. Bei anderen Auflösung als 640x480 müssen natürlich die Werte angepaßt werden.
Jetzt füttern wir den Clipper mit den eingestellten Werten:

ddrval = lpddClipper->SetClipList ((LPRGNDATA) &cliplist, 0);
if (ddrval != DD_OK)
{
   lpddClipper->Release();
   lpddClipper = NULL;
   lpddsBack->Release();
   lpddsBack = NULL;
   lpddsPrimary->Release();
   lpddsPrimary = NULL;
   lpDirectDraw->Release();
   lpDirectDraw = NULL;
   return false;
}

Das einzige, was wir jetzt noch machen müssen, ist, den Clipper an der Back Buffer anzuhängen. Dies geschieht mit der SetClipper-Methode:

ddrval = lpddsBack->SetClipper(lpddClipper);
if (ddrval != DD_OK)
{
   lpddClipper->Release();
   lpddClipper = NULL;
   lpddsBack->Release();
   lpddsBack = NULL;
   lpddsPrimary->Release();
   lpddsPrimary = NULL;
   lpDirectDraw->Release();
   lpDirectDraw = NULL;
   return false;
}

Und natürlich muß am Programmende wieder aufgeräumt werden (vor dem Back Buffer!):

if (lpddClipper != NULL)
{
   lpddClipper->Release();
   lpddClipper = NULL;
}

Umstruktierter Code
Bevor es im nächsten Teil weitergeht, habe ich den Code etwas umstrukturiert.
Ich habe den ganzen DirectDraw-Code in vier Funktionen gepackt. Hier die Funktionsnamen mit den Beschreibungen:

* ddInit (HWND hwnd)
Hier ist der ganze Initialisierungscode für DirectDraw.
* ddLastInits ()
Hier ist der ganze Code, der nicht in jedem Programm zu finden ist, also z.B. das Laden von Grafiken.
* ddDraw ()
Hier ist der Code, mit dem der Back Buffer gefüllt wird. Außerdem der Code zum Flippen.
* ddRelease ()
Der Code zum Aufräumen.

Besonders praktisch ist hier die ddRelease-Funktion, da wir nach jedem fehlgeschlagenen DirectDraw-Aufruf nur noch diese Funktion aufrufen müssen.
Schauen Sie sich die Funktionen einfach mal selber an!

Das noch nicht umstrukturierte Projekt können Sie hier herunterladen.
Das umstrukturierte Projekt können Sie hier herunterladen.

Teil 7