How to draw text on a bitmap in memory [no MFC]

2.5k views Asked by At

Can somebody explain how to draw text on a bitmap in memory? I have the following code, but I can't figure out how to do it.

case WM_PAINT:
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hWnd, &ps);
    HDC buffDC = CreateCompatibleDC(hdc);
    
    SelectObject(buffDC, hFnt);
    SetTextColor(buffDC, RGB(1, 1, 1));
    SetBkColor(buffDC, RGB(0, 255, 0));
    
    RECT rc;
    GetClientRect(hWnd, &rc);
    HBITMAP buffBitmap = CreateCompatibleBitmap(buffDC, rc.right, rc.bottom);

    int savedDC = SaveDC(buffDC);

    HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
    FillRect(buffDC, &rc, hBrush);
    DeleteObject(hBrush);

    //This is the part where I would like to draw to the bitmap
    TextOutA(buffDC, 0, 0, "Hello", 6);

    SelectObject(buffDC, buffBitmap);
    BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);
    RestoreDC(buffDC, savedDC);


    DeleteObject(buffBitmap);
    DeleteDC(buffDC);
    EndPaint(hWnd, &ps);
    break;
}

I have seen a lot of different solutions mostly using MFC, however I would like to avoid that approach if possible.

EDIT: I have checked the other already asked questions but I couldn't find one which would cover this without MFC.

My original problem was that I'm using a timer to call RedrawWindow and update the position of the text and make a sort of scrolling text which moves from right to left.

When I was in the testing process I have noticed that on some machines the app runs with up to 25% CPU usage and on some others it uses < 1%. I have tested the app one two machines with exactly the same specs running Windows 7 and the app ran on one with ~10% and the other with 0%.

(By the way my timer is getting called every 33ms and RedrawWindow uses RDW_UPDATENOW and I didn't handle the WM_ERASEBKGND message either :P Since WM_TIMER (as far as I know) is a low priority message I was not concerned about the timer causing the issue with the CPU usage.)

I started to think that maybe I should be using a bitmap and BitBlt it to the screen rather than just simply drawing to the dc and updating the x coordinate every time I repaint the screen.

Thanks

2

There are 2 answers

7
zett42 On BEST ANSWER

Before you can draw onto a bitmap, you have to select it into a memory device context.

Move SelectObject(buffDC, buffBitmap); before the first call to a drawing function, but usually as soon as possible after you created the bitmap.

In your sample code it appears suitable to insert it after the SaveDC() call so the original bitmap will be restored later when you call RestoreDC():

int savedDC = SaveDC(buffDC);
SelectObject(buffDC, buffBitmap);

As commenters noted, CreateCompatibleBitmap(buffDC, rc.right, rc.bottom) should be changed to CreateCompatibleBitmap(hdc, rc.right, rc.bottom). From the reference of CreateCompatibleBitmap():

When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the HDC that was used to create the memory device context

Finally a suggestion: If you just need a temporary bitmap (as in your sample code), there is a more efficient API available since Windows Vista. It is called the buffered paint API. MSDN does not appear to provide a good overview, here is a tutorial and the reference (all functions that have "BufferedPaint" in their name).

0
DiCoder On

Here is the Window Procedure which worked for me and is based on the answer from zett42. This piece of code is just for testing purposses as I cannot post the original source code of the application I'm working on due to work.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   static int xPos;
   const bool bIsBufferedPaint = true;
   switch (message)
   {
   case WM_CREATE:
   {
      BufferedPaintInit();
   }
   break;
   case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hdc = BeginPaint(hWnd, &ps);
         if(bIsBufferedPaint)
         {
            HDC newDC;
            RECT rc;
            RECT dstrc;
            GetClientRect(hWnd, &rc);
            dstrc = rc;
            dstrc.left = rc.right + xPos;
            HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &rc, BPBF_COMPATIBLEBITMAP, NULL, &newDC);
            if(hBufferedPaint)
            {
                BufferedPaintClear(hBufferedPaint, NULL);
                SetTextColor(newDC, RGB(0, 0, 0));
                DrawText(newDC, L"Using buffered paint", -1, &dstrc, DT_SINGLELINE | DT_VCENTER | DT_LEFT);
                Sleep(2);
                EndBufferedPaint(hBufferedPaint, TRUE);

            }
            else
            {
            // buffer paint did not work.
            }
        }
        else
        {
            HDC buffDC = CreateCompatibleDC(hdc);
            SetTextColor(buffDC, RGB(0, 0, 0));
            SetBkColor(buffDC, RGB(255, 255, 255));

            RECT rc;
            GetClientRect(hWnd, &rc);
            HBITMAP buffBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);

            int savedDC = SaveDC(buffDC);
            SelectObject(buffDC, buffBitmap);

            HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
            FillRect(buffDC, &rc, hBrush);
            DeleteObject(hBrush);
            std::string testText = "Not using the buffered paint API";
            TextOutA(buffDC, xPos, 0, testText.c_str(), testText.size());
            BitBlt(hdc, 0, 0, rc.right, rc.bottom, buffDC, 0, 0, SRCCOPY);

            RestoreDC(buffDC, savedDC);

            DeleteObject(buffBitmap);
            DeleteDC(buffDC);
         }

        EndPaint(hWnd, &ps);
        }
        break;
   case WM_ERASEBKGND:
       return 1;
    case WM_TIMER:
        {
        switch(wParam)
        {
        case TIMER1:
            {
                xPos--;
                RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ERASE);
                if(bIsBufferedPaint)
                {
                    if(xPos <= -500)
                        xPos = 0;
                }
                else
                {
                    if(xPos <= -50)
                        xPos = 1000;
                }

            }
            break;
        }
        }
        break;
    case WM_NCDESTROY:
    {
         BufferedPaintUnInit();
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:

        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}