Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations John Tel on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Double Buffer GDI Graphics 5

Status
Not open for further replies.

gmmastros

Programmer
Feb 15, 2005
14,909
US
I am trying to use a double buffer technique for drawing shapes, and I just can't seem to get it right. Can someone please help me figure this out.

Currently, this will draw a single line on a form.

Code:
Option Explicit

Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDc As Long) As Long
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
 
Private Declare Function LineTo Lib "gdi32" (ByVal hDc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Declare Function MoveToEx Lib "gdi32" (ByVal hDc As Long, ByVal X As Long, ByVal Y As Long, lpPoint As Any) As Long
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
Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long

Private Const PS_SOLID = 0

Private Sub Command1_Click()
    
    Dim redPen As Long
    
    redPen = CreatePen(PS_SOLID, 15, vbRed)
    DeleteObject SelectObject(Me.hDc, redPen)
    MoveToEx Me.hDc, 10, 10, 0
    LineTo Me.hDc, 210, 210
    
    Me.Refresh

End Sub

Private Sub Form_Load()
    
    Me.AutoRedraw = True

End Sub

I've read that double buffering the drawing surface will speed up the drawing process, which is my ultimate goal here. So, if there is another technique that is faster than this, I'd be interested in that too.



-George

"the screen with the little boxes in the window." - Moron
 
It looks like you have a reference that uses old techniques. (Not double-buffering, but the BitBlt API.) Look up the PaintPicture method in your MSDN VB documentation.
 
>old techniques

Er ... no. BitBlt is simply the raw GDI API that PaintPicture basically wraps, not something old. And it only works with images that are held in a VB Picture/IPicture/IPictureDisp/StdPic object (ignore the VB documentation's comment that this must be the Picture property of a Form or PictureBox; it's wrong) and can only render them to limited variety of destinations. BitBlt itself is not so limited. And is faster.

>AutoRedraw = True

What you are doing here is activating VB's version of double buffering

>I've read that double buffering the drawing surface will speed up the drawing process

Um ... not necessarily. MNore generally it is a technique to eliminate visual rendering artifacts such as flickering, shearing or tearing. The idea is that you do all your drawing to an offscreen buffer, and when you have completed all the drawing you then copy that buffer to the screen all in one go. In Windows, using GDI, this means that we create a memory DC that is compatible with the screen DC, and then draw to that compatible DC. Then we blit from it to the screen. Because our memory DC is much simpler than the screen DC (no worrying about clipping or refreshing or talking too much to graphics hardware) many of our GDI operations will indeed be faster than the same operations carried out on the screen DC (however speed gains may possibly be outweighed by the time it takes to blit, depending on how large an image you are moving)
 
>BitBlt is simply the raw GDI API that PaintPicture basically wraps, not something old.

"Old" in the sense that the PaintPicture wrapper was introduced to enable programmers to draw using VB without resorting to API calls. The PaintPicture wrapper is "new" as of VB version 5.
 
VB4, if I recall.

And the reason I picked up on the point was because the clear implication in your post (to me at least) that BitBlt is outdated and passe.

Whereas the reality is that PaintPicture is slow and limited in comparison to BitBlt because the VB developers at MS decided that VB programmers wouldn't actually need the full power of BitBlt and so removed functionality in the interface they exposed - and also had to include a bunch of set up and clean up code (involving creating and then throwing away device contexts) that is called every time the method is invoked. "New" does not always mean better ...

Sure, there are good uses for PaintPicture - but doing your own double-buffering to get try and get a performance increase isn't one of them.
 
(and I wasn't as accurate as I could have been [banghead], it's actually the slower StretchBlt that PaintPicture wraps)
 
strongm,

Based on your suggestions, I finally got this work in a test environment.

My app draws a map (Streets, parks, rivers, streams, etc...).

map.jpg


Currently, I am drawing directly to a picture box control to do this. I am using gdi API calls to draw the regions and lines. Most lines are drawn twice, once with a thick line and then again with a thinner one. Currently it takes about 0.4 seconds to redraw the map, and I'd like to make it faster.

In your opinion, should I continue to investigate the double buffering techniques, or do you think it would be a waste of my time?

-George

"the screen with the little boxes in the window." - Moron
 
<clear implication in your post (to me at least)
Seemed that way to me as well.

Two questions please strongm: What's a DC? What's blitting? Links will be fine.

<I finally got this work in a test environment.
What changes did you make to your code?
 
A DC is a Device Context:
And blitting the is the act of performing a fast block transfer of the bits that make up an image from one place to another (and performing some optional, generally simple, merge operation if desired):
> should I continue to investigate the double buffering techniques

I'd keep investigating.
 
Bob,

You're right. I should have posted the code. Here is the simplest example of double buffering that I could cobble together. This is based on example I found on the internet and direction/guidance from strongm's first post.

Code:
Option Explicit

Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long

Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, lpPoint As Any) As Long
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
Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long

Private Const PS_SOLID = 0

Private Const BLACKNESS = &H42
Private Const DSTINVERT = &H550009
Private Const MERGECOPY = &HC000CA
Private Const MERGEPAINT = &HBB0226
Private Const NOTSRCCOPY = &H330008
Private Const NOTSRCERASE = &H1100A6
Private Const PATCOPY = &HF00021
Private Const PATINVERT = &H5A0049
Private Const PATPAINT = &HFB0A09
Private Const SRCAND = &H8800C6
Private Const SRCCOPY = &HCC0020
Private Const SRCERASE = &H440328
Private Const SRCINVERT = &H660046
Private Const SRCPAINT = &HEE0086
Private Const WHITENESS = &HFF0062

Private Sub Command1_Click()
    
    Dim redPen As Long
    Dim memDC As Long
    Dim hMemBMP As Long
    
    memDC = CreateCompatibleDC(hdc)
    hMemBMP = CreateCompatibleBitmap(hdc, Me.ScaleWidth, Me.ScaleHeight)
    Call SelectObject(memDC, hMemBMP)
    
    redPen = CreatePen(PS_SOLID, 1, vbRed)
    DeleteObject SelectObject(memDC, redPen)
    
    MoveToEx memDC, 10, 10, 0
    LineTo memDC, 210, 210
    
    Call BitBlt(hdc, 0, 0, Me.ScaleWidth, Me.ScaleHeight, memDC, 0, 0, vbSrcAnd)
    
    Call DeleteObject(hMemBMP)
    Call DeleteDC(memDC)
    
End Sub

Private Sub Form_Load()
    
    Me.AutoRedraw = False
    Me.ScaleMode = vbPixels

End Sub

-George

"the screen with the little boxes in the window." - Moron
 
<DC is a Device Context
Doh!

Thanks for the explanations, strongm, and George, thanks for the post. Now I can archive this....
 
George,

Of the 0.4s you mentioned, how much is spent creating the image on the back buffer, and how much blitting it to the screen?

Tony
 
His 0.4 seconds are for directly rendering to a picture box, not a back buffer, as far as I can tell from his post
 
Tony,

As you can imagine, drawing a map is complicated business. [smile]

The 0.4 seconds is an average because some areas have more roads than others (urban areas compared to rural areas). The 0.4 seconds indicates the entire time to draw the map, which includes determining which items to draw and actually drawing them.

The 0.4 figure was for drawing directly to a picture box, not to a back buffer. Converting this algorithm to use a back buffer will probably take several days to implement because the drawing routines are scattered throughout the code.

-George

"the screen with the little boxes in the window." - Moron
 
Sorry, I ought to have read more closely. What I was actually trying to get at is how much time is spent calculating / determining what goes where, and how much actually drawing? Can you split it that way? Is the "calc" or "draw" part the limiting factor? If you could reduce the "draw" time to zero, what would be your limiting frame rate?

If you are currently drawing direct to the PB, how many separate draw operations are you (typically) doing for a single image?

I've done code like this in the past, where I've created an array of rgb values in memory, based on ray history files (ray trace information) then converted that to a bitmap (actually a DIB if I remember correctly) and associated the bitmap with a PB. I'd need to check, but I think the best rate I got was about 30Hz for a 256 * 256 image. Would that be any use to you?

Tony
 
Tony,

I will attempt to get the information you ask for. It'll be a bit difficult to get because of the way it is currently structured. For example, to draw the local roads:

1. I first determine which roads are visible (and put the visible roads in to an array).

2. I draw them all in a 6 pixel thick black line.

3. Re-draw them in a 4 pixel thick white line.

Then, I move on to the next 'layer' and to draw other things, like rivers, streams, railroads, parks, and county boundaries (not in this order).

Extracting the time to determine what should be drawn vs. the time to draw them will take a bit of work to do. I'll post back once I've figured it out.

-George

"the screen with the little boxes in the window." - Moron
 
Given the vector-based nature of what you are doing, one other avenue of investigation might be an enhanced metafile.

Basically, you do all your GDI drawing to the meatfile (which has it's own DC), and then you can play the metafile back repeatedly (or save it to disk for later use) - and with the advantage against blitting that you can zoom, pan and clip without losing resolution
 
Thanks again strongm.

Do have have any helpful links, or possibly an example that you could share?



-George

"the screen with the little boxes in the window." - Moron
 
Helpful links? Not really, beyond the MSDN documentation ( EMFs seem to be sadly neglected for the most part by VB programmers ... so here's something I knocked up for you as an illustration (you'll need a form with 3 picture boxes and a command button). It looks a lot worse than it actually is:
Code:
[blue]Option Explicit

Private Type RECT
        Left As Long
        Top As Long
        Right As Long
        Bottom As Long
End Type

' DC stuff
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long

' EMF stuff
Private Declare Function CloseEnhMetaFile Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateEnhMetaFile Lib "gdi32" Alias "CreateEnhMetaFileA" (ByVal hdcRef As Long, ByVal lpFileName As String, lpRect As RECT, ByVal lpDescription As String) As Long
Private Declare Function DeleteEnhMetaFile Lib "gdi32" (ByVal hEMF As Long) As Long
Private Declare Function PlayEnhMetaFile Lib "gdi32" (ByVal hdc As Long, ByVal hEMF As Long, lpRect As RECT) As Long

' Misc GDI support stuff
Private Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As RECT) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
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

' Drawing stuff
Private Declare Function CreatePen Lib "gdi32" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As Long
Private Declare Function MoveToEx Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal lpPoint As Any) As Long 'POINTAPI) As Long
Private Declare Function LineTo Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long

Private Const HORZRES = 8            '  Horizontal width in pixels
Private Const HORZSIZE = 4           '  Horizontal size in millimeters
Private Const VERTRES = 10           '  Vertical width in pixels
Private Const VERTSIZE = 6           '  Vertical size in millimeters
Private Const PS_SOLID = 0

Private Sub Command1_Click()
    Dim hdcMeta As Long
    Dim hEMF As Long
    Dim myRect As RECT
    Dim iWidthMM As Long
    Dim iHeightMM As Long
    Dim iWidthPels As Long
    Dim iHeightPels As Long
    Dim lp As Long
    Dim lDrawWidth As Long
    Dim hPen As Long
    Dim hPenOld As Long
    Dim PenColor As Long
    Dim memDC As Long
    
    ' Get recording area size.
    GetClientRect Picture1.hwnd, myRect
    
    ' Need to convert to logical units
    iWidthMM = GetDeviceCaps(Picture1.hdc, HORZSIZE)
    iHeightMM = GetDeviceCaps(Picture1.hdc, VERTSIZE)
    iWidthPels = GetDeviceCaps(Picture1.hdc, HORZRES)
    iHeightPels = GetDeviceCaps(Picture1.hdc, VERTRES)
    myRect.Left = (myRect.Left * iWidthMM * 100) / iWidthPels
    myRect.Top = (myRect.Top * iHeightMM * 100) / iHeightPels
    myRect.Right = (myRect.Right * iWidthMM * 100) / iWidthPels
    myRect.Bottom = (myRect.Bottom * iHeightMM * 100) / iHeightPels
    
    ' Now create an enhanced metafile on a memory DC
    memDC = CreateCompatibleDC(Picture1.hdc)
    hdcMeta = CreateEnhMetaFile(memDC, vbNullString, myRect, vbNullString)

    ' Draw a 'road layout' since that is in keeping with the thread
    For lDrawWidth = 6 To 4 Step -2
        ' Same layout each time
        Rnd -1
        Randomize 1
        If lDrawWidth = 6 Then PenColor = vbBlack Else PenColor = RGB(255, 255, 225)
        hPen = CreatePen(PS_SOLID, lDrawWidth, PenColor)
        hPenOld = SelectObject(hdcMeta, hPen)
        
        ' Random 'roads'
        MoveToEx hdcMeta, Rnd() * Picture1.ScaleWidth, Rnd() * Picture1.ScaleHeight, ByVal 0&
        For lp = 0 To 75
            LineTo hdcMeta, Rnd() * Picture1.ScaleWidth, Rnd() * Picture1.ScaleHeight
        Next
    Next
        
    ' Ok, get a handle to the playable enhanced metafile
    hEMF = CloseEnhMetaFile(hdcMeta)
    
    ' Note that at this point nothing is visible - so far we've only recorded the EMF
    
    ' Retrieve playback area (same as original recording area)
    GetClientRect Picture1.hwnd, myRect
    
    
    ' OK, now we'll play back the EMF in a variety of ways
    
    ' Play back as originally sized and positioned into Picture1
    PlayEnhMetaFile Picture1.hdc, hEMF, myRect

    ' Zoom and playback into Picture2
    myRect.Right = myRect.Right * 2
    myRect.Bottom = myRect.Bottom * 2
    PlayEnhMetaFile Picture2.hdc, hEMF, myRect
    
    ' Pan and playback into Picture3
    myRect.Left = -50
    myRect.Top = -50
    PlayEnhMetaFile Picture3.hdc, hEMF, myRect
    
    ' A bit of a cleanup
    If hdcMeta Then
        SelectObject hdcMeta, hPenOld
        DeleteObject hPen
        DeleteEnhMetaFile hEMF
        DeleteDC hdcMeta
    End If

End Sub

Private Sub Form_Load()
    Picture1.ScaleMode = vbPixels
End Sub[/blue]
 
Oh - it's probably also worth noting that EMFs (unless you include a Clear in the recording) work as overlays. SO you can overlay your GDI drawing over an existing image or, more interestingly in your case, break the map into seperate EMF layers and just play back the layers the user want/needs
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top