DirectX:DirectDraw:Tutorials:VB:DX7:Efficient Line Drawing and Text Display

From GPWiki
Jump to: navigation, search

My Spidey Sense is tingling... I can tell what the newbies are thinking. "DirectX comes with simple methods for doing line and text drawing, we don't need a tutorial on that!" Ahh, but the DirectX methods are inefficient! I have no idea why Microsoft saw fit to do this, but whenever you call the DirectDrawSurface7.DrawLine or DirectDrawSurface7.DrawText method, you're also locking and unlocking the surface! This is fine if you only want to draw one or two things... but if you need to draw a fair amount, you'll start to notice the overhead. You're unneccessarily locking and unlocking the surface each time--a costly operation. Microseconds add up; don't doubt it.

The alternative? GDI! You may have it in your mind that API calls are always slower than DirectX functions, but it isn't true. Using the drawing functions inherent in Windows' GDI you can remove the need for multiple locks. Lock the surface once (by getting its DC) and unlock it once (by releasing its DC) and perform all needed operations in between.

Now we need to learn the API calls that draw lines and text.

Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, _

   ByVal y As Long) As Long

Lets start out nice and easy :) The LineTo function draws a line from the current X,Y coordinates to the ones passed as arguments. Where are these "current" coordinates? Well, they're the X,Y coordinates of the last GDI operation that took place. This is convenient in some cases (for drawing enclosed shapes, for example), but in most cases, it's not. We need to be able to move the current coordinates:

Private Declare Function MoveToEx Lib "gdi32.dll" (ByVal hdc As Long, ByVal x As Long, _

   ByVal y As Long, lpPoint As POINT_TYPE) As Long

Private Type POINT_TYPE

 x As Long
 y As Long

End Type

Just pass the X and Y values you'd like to be "current" to the MoveToEx function. You also need to pass a variable of POINT_TYPE, just leave it empty.

Ok, lines are a snap. What about circles?

Private Declare Function Ellipse Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, _

   ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long

Ellipse? Yes, a circle is just a special case of an ellipse. You can use the Ellipse function to draw any ellipse (or circle) you desire. The coordinates you pass form a rectangle which will form the boundaries for your ellipse. If you want a circle, simply ensure that the height and width of your "rectangle" are equal.

Private Declare Function Arc Lib "gdi32" (ByVal hdc As Long, ByVal x1 As Long, _

   ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal X3 As Long, _
   ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long) As Long

Sometimes a whole ellipse is just too much.. bring on the arc! The first four arguments form a rectangle that defines an ellipse. This ellipse is then "cut" by the last 4 parameters. This takes a bit of thinking.. throw on your imagination hat. Imagine a line originating from the center of your ellipse. This line must intersect with the coordinates x3 and y3. Imagine a similar line that intersects instead with the coordinates x4 and y4. These two lines divide the elipse into two segments. Travelling counter-clockwise from the first line to the second line will dictate the segment that remains. BAM! An arc.

Private Declare Function TextOut Lib "gdi32" Alias "TextOutA" (ByVal hdc As Long, ByVal _

   x As Long, ByVal y As Long, ByVal lpString As String, ByVal nCount As Long) As Long

My good buddy, old faithful, TextOut. Simply pass the coordinates at which you'dlike your text to be drawn, the string you'd like printed, and the length of the string (in characters). That's all there is to it.

Private Declare Function SetTextColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor _

   As Long) As Long

No doubt white text gets a little tiresome after a while.. that's why god invented SetTextColor. Tell it the Long colour value you'd like to see, and you shall see it! VB constants like vbGreen and others will work just fine.

Private Declare Function SetBkMode Lib "gdi32" (ByVal hdc As Long, ByVal nBkMode As _

   Long) As Long

Const TEXT_TRANSPARENT = 1 Const TEXT_OPAQUE = 2

I don't know about you.. but I don't like having my text blot out whatever's behind it with an opaque background. You can change this with SetBkMode. Send the constant TEXT_TRANSPARENT to this function, and the nasty backgrounds won't bother you anymore.

Private Declare Function SetBkColor Lib "gdi32" (ByVal hdc As Long, ByVal crColor As _

   Long) As Long

Or perhaps you do like a nasty opaque background! Who am I to judge? You can change the colour of the background using the SetBkColor function. It's exactly the same as the SetTextColor function, but it only affects the text's backdrop colour.

Now things get a bit dicey. If you want to change the pen style (to draw non-solid lines, thick lines, or coloured lines), or the font, you have to go through a few additional steps. You can't simply assign the pen or font of a DC, you have to create the pen or font and select it into that DC.

Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As _

   Long) As Long

Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

You MUST remember to store the old pen or font and select it back into the DC when you're done. Also, deleting the created pen or font after you've finished using them is required. If you fail to do these things, you'll find that your program leaks memory badly!

Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth _

   As Long, ByVal crColor As Long) As Long

Const PS_SOLID = 0 Const PS_DASH = 1 Const PS_DOT = 2 Const PS_DASHDOT = 3 Const PS_DASHDOTDOT = 4

There are a couple different pen styles you can use.. and obviously you can change the colour and width of the pen as well.

Private Declare Function CreateFont Lib "gdi32" Alias "CreateFontA" (ByVal H As Long, _

   ByVal W As Long, ByVal E As Long, ByVal O As Long, ByVal W As Long, ByVal i As Long, _
   ByVal u As Long, ByVal s As Long, ByVal C As Long, ByVal OP As Long, ByVal CP As Long, _
   ByVal Q As Long, ByVal PAF As Long, ByVal F As String) As Long

Ugly! There are a lot of fields here.. some you'll never need to change, so I won't describe them. There are a few important ones however. The first 2 arguments are the font's height and width. The fourth is the weight, fifth is for italics, and sixth is for underline. The last argument is a font name string. "Arial" or "Courier" are examples of appropriate values. The other arguments can be handled effectively by a few constants (see source).

So, now you know how these API calls work in theory. Check out this sample source code to see how it works in real life!

For further discussion, see the talk page for this tutorial.