Understanding BitBlt

This is a discussion on Understanding BitBlt within the Windows Programming forums, part of the Platform Specific Boards category; Hi, I'm trying to figure out how, once you've drawn stuff in a window, to keep it in there semi-permanently. ...

  1. #1
    Registered User
    Join Date
    Sep 2007
    Posts
    127

    Understanding BitBlt

    Hi,

    I'm trying to figure out how, once you've drawn stuff in a window, to keep it in there semi-permanently. After reading around a bit it seems that you can use BitBlt to restore stuff that was wiped when the window was last updated. But after looking it up on msdn, I'm having a lot of trouble understanding how to use it.

    They give an example function describing how to use it here (scroll down about 3/4 of the way):

    BitBlt

    That example includes the line:

    Code:
    hbmOld = SelectObject(hdcSrc, hbm);
    Are calls to SelectObject like that actually modifying hdcDst and hdcSrc? That's how it seems.

    But the problem is, when I try to add this into my basic ass windows program, the compiler tells me there's a problem with lines such as:

    Code:
    hbmOld = SelectObject(hdcSrc, hbm);
    It says:

    "cannot convert from 'HGDIOBJ' to 'HBITMAP' Conversion from 'void*' to pointer to non-'void' requires an explicit cast"

    Maybe it's something to do with how I declared hbm:

    Code:
    HBITMAP hbm = CreateCompatibleBitmap(hdc, 100, 100);
    If anyone can help me understand any of what's going on with this I'd appreciate it. Thanks.

  2. #2
    Registered User
    Join Date
    Mar 2005
    Location
    Juneda
    Posts
    287
    "...Are calls to SelectObject like that actually modifying hdcDst and hdcSrc?..."

    (Where's declared hdcDst?) SelectObject does not modify the objects, but I think it works like a state machine: you are telling the drawer that from now it should use the object you give it; is for that reason that you have to keep a ptr to the old object, to restore to the default when you have finished painting your own stuff; so after drawing it all you have to reselect the old values. While drawing bitmaps I'm not sure if that would be necessary, but if you change the font, the text color, etc it is helpful, (and I think that's necessary to ownerdraw-type messages of common controls).

    For the error, I think it might be something of your compiler, and as the error line suggest, maybe it will solve typecasting:

    Code:
    hbmOld=(HBITMAP)SelectObject(hdcSrc,hbm);
    Hope that helps
    Niara

  3. #3
    Super Moderator VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,586
    When you draw you should draw to an offscreen DC. Then in the paint after the drawing is done you BitBlt the offscreen DC into the actual window DC. It's double buffering in the messed-up Windows GDI way. To do this correctly you would turn the auto double-buffering off or you will get some strange artifacts.

    Anytime you draw to the DC you will see it stay there. However if a re-size operation is done or a repaint and you are not accounting for this the WM_SIZE or WM_PAINT message will force a refresh which will call your OnPaint() method which means if nothing is there you won't see anything being redrawn. GDI is far different from how you would draw things in other APIs and you do not have to draw items on a per-frame basis. If your OnPaint() is drawing correctly then any messages that would force a refresh or redraw should be handled just fine.
    Last edited by VirtualAce; 07-25-2009 at 11:13 AM.

  4. #4
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,856
    Think of the HDC as a 'holder'.

    The HDC has GDI (graphical device interface) objects used for drawing inside (pen, font, brush etc).

    PCs have very limited GDI memory and will stop being able to redraw if too much leaks. (.NET MSVC IDEs (not v6) are much safer as GDI objects 'decay' and are garbage collected).

    When you 'get' or 'create' a HDC it already has default GDI objects inside.
    Sometimes GDI objects do not delete properly when 'inside' a HDC.
    Sometimes the HDC does not delete properly unless it has all its original (default) GDI objects in it.

    In other words, 'put the HDC back the way you found it!'.

    If you want to use a different font, you create the font (which allocs mem)

    To use the font you 'select' the new font into your HDC.

    This 'pushes' out the currrent font.

    You must catch this font (as the return from SelectObject() )

    When you have finished with the new font, you 'push' the original font back into the HDC (with SelectObject())

    You can then delete both the new font and HDC (freeing any GDI mem they used).

    Search here for lots more code, plenty posted for this kind of stuff.

    Code:
    hbmOld=(HBITMAP)SelectObject(hdcSrc,hbm);
    This will fix the error and is very common (changing from .cpp files to .c files will also do it but is not usually desirable).

    C++ requires an 'explicit' cast, ie must use the exact same type of pointer (HBITMAP) not just any pointer (void*).
    [C is not as strict on casting pointers as C++ and does not require the cast.]
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  5. #5
    Registered User
    Join Date
    Sep 2007
    Posts
    127
    Thanks guys.

    I've been having a bit more success with this today. In my basic program, I've added the following, and it works to reptedly copy a random line drawn on the left of the screen over to the right.

    At the top:

    Code:
    #include <time.h>
    #include <ctime>
    At the start of MainWndProc:

    Code:
        HDC hdcSrc, hdcDst;
        HBITMAP hbm, hbmOld, hbmOld2, hbmNew;
        BITMAP bm;
    In WM_PAINT:

    Code:
            
            hdc = BeginPaint(hwnd, &ps);
            ::MoveToEx (hdc, rand() % 300, rand() % 300, 0);
            ::LineTo (hdc, rand() % 300, rand() % 300);
            hdcSrc = hdc;
            hdcDst = hdc;
            hbm = CreateBitmap(200, 200, 1, 24, NULL);
            GetObject(hbm, sizeof(bm), &bm);
            hbmOld = (HBITMAP)SelectObject(hdcSrc, hbm);
            hbmNew = (HBITMAP)CreateBitmap( bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel,NULL);
            hbmOld2 = (HBITMAP)SelectObject(hdcDst, hbmNew);
            BitBlt(hdcDst, 300, 100, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);
            SelectObject(hdcSrc, hbmOld);
            DeleteDC(hdcSrc);
            DeleteDC(hdcDst);
            EndPaint(hwnd, &ps);
    But there's something I don't understand. When I'm assigning values to hdcSrc and hdcDst, in the msdn example for BitBlt, it says you can use:

    Code:
        HDC hdcSrc = CreateCompatibleDC(NULL);
        HDC hdcDst = CreateCompatibleDC(NULL);
    According to the entry for CreateCompatibleDC, using NULL as a parameter should make it use the currently selected window. However, if I use these lines in my program, even if I keep my window selected, it doesn't work. I just get one line on the left and it doesn't copy it. Hence I've done:

    Code:
            hdcSrc = hdc;
            hdcDst = hdc;
    So why doesn't CreateCompatibleDC(NULL) work for hdcSrc and hdcDst?

    Thanks again guys, I feel like I've made some good progress with this now.

  6. #6
    Registered User
    Join Date
    Sep 2007
    Posts
    127
    Quote Originally Posted by Bubba View Post
    When you draw you should draw to an offscreen DC. Then in the paint after the drawing is done you BitBlt the offscreen DC into the actual window DC. It's double buffering in the messed-up Windows GDI way. To do this correctly you would turn the auto double-buffering off or you will get some strange artifacts.

    Anytime you draw to the DC you will see it stay there. However if a re-size operation is done or a repaint and you are not accounting for this the WM_SIZE or WM_PAINT message will force a refresh which will call your OnPaint() method which means if nothing is there you won't see anything being redrawn. GDI is far different from how you would draw things in other APIs and you do not have to draw items on a per-frame basis. If your OnPaint() is drawing correctly then any messages that would force a refresh or redraw should be handled just fine.
    How would you go about creating an off-screen HDC? I tried doing this under WM_PAINT, but the screen just remained blank.

    Code:
            hdc = BeginPaint(hwnd, &ps);
            hdc2 = CreateCompatibleDC(hdc);
            ::MoveToEx (hdc2, rand() % 300, rand() % 300, 0);
            ::LineTo (hdc2, rand() % 300, rand() % 300);
            hdcSrc = hdc2;
            hdcDst = hdc;
            hbm = CreateBitmap(200, 200, 1, 24, NULL);
            GetObject(hbm, sizeof(bm), &bm);
            hbmOld = (HBITMAP)SelectObject(hdcSrc, hbm);
            hbmNew = (HBITMAP)CreateBitmap( bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel,NULL);
            hbmOld2 = (HBITMAP)SelectObject(hdcDst, hbmNew);
            BitBlt(hdcDst, 300, 100, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);
            SelectObject(hdcSrc, hbmOld);
            DeleteDC(hdcSrc);
            DeleteDC(hdcDst);
            EndPaint(hwnd, &ps);
            return 0;
    Thanks.

  7. #7
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,856
    Drawing is slow, so have to try and create the GDIs only once.

    We also try to do the drawing offscreen and when finished, post to screen with a paint msg.

    If you declare a variable in the callback (and want it to retain the value each msg) you must declare the variable as a static.

    I use something like....

    Code:
    static HDC hdcScr = NULL;
    static HBITMAP hbmpScr = NULL, hbmpOrig = NULL;
    
    
    //WM_CREATE
    //create our DC, BMP and set it up
    
    //get the size of the whole screen [GetDeviceCaps() with HORZRES|VERTRES]
    //create a mem DC [CreateCompatibleDC( NULL )]
    //create a BMP [CreateCompatibleBitmap( memDC)]
    //select the BMP into mem DC, catching return [SelectObject]
    //fill the DC with a solid colour [FillRect GetStockObject(WHITE_BRUSH)]
    
    //WM_DESTROY
    //clean up our DC and BMP
    
    //Select original BMP back into mem DC
    //delete comp BMP
    //delete mem DC
    
    //WM_PAINT
    //draw our mem DC to the paint DC
    PAINTSTRUCT ps;
    
    hdc = BeginPaint(hwnd, &ps);
    BitBlt(hdc, 
    ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom -   ps.rcPaint.top, //only redraw the invalid area, not the whole screen.
    hdcScr, //from mem DC
    ps.rcPaint.left, ps.rcPaint.top, //assuming no offset for the image
    SRCCOPY);
    
    EndPaint(hwnd, &ps);
    
    
    //MSG to draw new line (ie mouse click)
    //draw a new line as required 
    
    //draw line (you have the code to do this)
    //call for a paint msg to draw line to screen
    
    InvalidateRect( hWnd, NULL, FALSE); //redraw whole client area but not background
    UpdateWindow( hWnd); //bypass the OS msg queue and send directly tothis callback (is faster)
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  8. #8
    Registered User
    Join Date
    Sep 2007
    Posts
    127
    Quote Originally Posted by novacain View Post
    Drawing is slow, so have to try and create the GDIs only once.

    We also try to do the drawing offscreen and when finished, post to screen with a paint msg.

    If you declare a variable in the callback (and want it to retain the value each msg) you must declare the variable as a static.

    I use something like....

    Code:
    static HDC hdcScr = NULL;
    static HBITMAP hbmpScr = NULL, hbmpOrig = NULL;
    
    
    //WM_CREATE
    //create our DC, BMP and set it up
    
    //get the size of the whole screen [GetDeviceCaps() with HORZRES|VERTRES]
    //create a mem DC [CreateCompatibleDC( NULL )]
    //create a BMP [CreateCompatibleBitmap( memDC)]
    //select the BMP into mem DC, catching return [SelectObject]
    //fill the DC with a solid colour [FillRect GetStockObject(WHITE_BRUSH)]
    
    //WM_DESTROY
    //clean up our DC and BMP
    
    //Select original BMP back into mem DC
    //delete comp BMP
    //delete mem DC
    
    //WM_PAINT
    //draw our mem DC to the paint DC
    PAINTSTRUCT ps;
    
    hdc = BeginPaint(hwnd, &ps);
    BitBlt(hdc, 
    ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom -   ps.rcPaint.top, //only redraw the invalid area, not the whole screen.
    hdcScr, //from mem DC
    ps.rcPaint.left, ps.rcPaint.top, //assuming no offset for the image
    SRCCOPY);
    
    EndPaint(hwnd, &ps);
    
    
    //MSG to draw new line (ie mouse click)
    //draw a new line as required 
    
    //draw line (you have the code to do this)
    //call for a paint msg to draw line to screen
    
    InvalidateRect( hWnd, NULL, FALSE); //redraw whole client area but not background
    UpdateWindow( hWnd); //bypass the OS msg queue and send directly tothis callback (is faster)
    Thanks. I tried implementing that stuff, and got it to draw a load of random lines which stayed on the screen. Here's my code so far.

    Code:
        int x,y;
        static HDC hdcScr = NULL;
        static HBITMAP hbmpScr = NULL, hbmpOrig = NULL;
        static RECT r;
    Code:
        case WM_CREATE:
            try
            {
                pCtrl = new Controller (hwnd, reinterpret_cast<CREATESTRUCT *> (lParam));
                // Store pointer to Controller inside Window
                WinSetLong<Controller *> (hwnd, pCtrl);
                SetTimer(hwnd, IDT_TIMER1, 500, (TIMERPROC) NULL); // no timer callback
                // Text output stuff.
                hdc = GetDC(hwnd); 
                ReleaseDC(hwnd, hdc); 
                hdcScr = CreateCompatibleDC(NULL);
                x = GetDeviceCaps (hdcScr, HORZRES);
                y = GetDeviceCaps (hdcScr, VERTRES);
                hbmpOrig = CreateCompatibleBitmap(hdcScr, x, y);
                hbmpScr=(HBITMAP)SelectObject(hdcScr,hbmpOrig);
                SetRect(&r, 0, 0, x, y); 
                Rectangle(hdcScr, r.left, r.top, r.right, r.bottom);
                FillRect(hdcScr, &r, (HBRUSH)GetStockObject(WHITE_BRUSH));
            }
    Code:
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            BitBlt(hdc, 
            ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, 
            ps.rcPaint.bottom - ps.rcPaint.top, //only redraw the invalid area, not the whole screen.
            hdcScr, //from mem DC
            ps.rcPaint.left, ps.rcPaint.top, //assuming no offset for the image
            SRCCOPY);
            EndPaint(hwnd, &ps);
            return 0;
    Code:
        case WM_TIMER:
            switch (wParam)
                case IDT_TIMER1:
                {    
                    hdc = GetDC(hwnd); 
                    ::MoveToEx (hdcScr, rand() % 300, rand() % 300, 0);
                    ::LineTo (hdcScr, rand() % 300, rand() % 300);
                    ReleaseDC(hwnd, hdc);
                    InvalidateRect(hwnd, NULL, FALSE); 
                    UpdateWindow(hwnd);
    		    }
            return 0;
    Does some of that seem ok? I know I haven't deleted stuff properly but I'm just trying to get the basics going first. It seems to do what it's supposed to, but there's something I don't understand.

    Say I comment out everything after "ReleaseDC(hwnd, hdc);" in WM_CREATE, and comment out everything between "BeginPaint" and "EndPaint" in WM_PAINT. Then, if I change the hdcScr's in MoveToEx and LineTo to hdc, the lines will still draw, and stay on the screen.

    Surely they should just appear and then disappear each time WM_PAINT gets called though? If stuff's not being copied to/from hdcSrc, why do they still stay on the screen?

    This stuff is really bloody confusing, so thanks for your help- it's appreciated.

  9. #9
    train spotter
    Join Date
    Aug 2001
    Location
    near a computer
    Posts
    3,856
    Quote Originally Posted by bengreenwood View Post
    Surely they should just appear and then disappear each time WM_PAINT gets called though?
    Try moving another app/dlg over the top of yours (even moving it if not full screen). I don't think the lines will stay.

    Paint msgs can be generated outside your app (by other apps etc). Without the mem DC and WM_PAINT the lines will not stay.

    Quote Originally Posted by bengreenwood View Post
    If stuff's not being copied to/from hdcSrc, why do they still stay on the screen?
    You are drawing to the screen, until the background is erased they will stay.

    try changing

    InvalidateRect(hwnd, NULL, FALSE);

    to

    InvalidateRect(hwnd, NULL, TRUE); //erase the background as well
    "Man alone suffers so excruciatingly in the world that he was compelled to invent laughter."
    Friedrich Nietzsche

    "I spent a lot of my money on booze, birds and fast cars......the rest I squandered."
    George Best

    "If you are going through hell....keep going."
    Winston Churchill

  10. #10
    Registered User
    Join Date
    Sep 2007
    Posts
    127
    Yeah, you were right. So is there a benefit to using FALSE with InvalidateRect instead of TRUE? I ask because that's how you did it the first time and I've seen TRUE used with it before. Or is it that it just alters whether the background erases and doesn't really affect anything else? The only thing is, I did notice it flicker a bit with TRUE. Thanks.

    Also did the code I used seem ok? To be honest I was pretty surprised it worked because it was the first time I'd used most of that stuff.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Need help understanding a problem
    By dnguyen1022 in forum C++ Programming
    Replies: 2
    Last Post: 04-29-2009, 04:21 PM
  2. trouble understanding the source file structure
    By Mario F. in forum C++ Programming
    Replies: 5
    Last Post: 05-26-2006, 06:46 PM
  3. understanding recursive functions
    By houler in forum C Programming
    Replies: 7
    Last Post: 12-09-2004, 11:56 AM
  4. another BitBlt question..
    By btq in forum Windows Programming
    Replies: 6
    Last Post: 10-11-2002, 04:28 PM
  5. Problems with BitBlt
    By Isometric in forum Windows Programming
    Replies: 6
    Last Post: 02-05-2002, 08:20 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21