0

Context :

  • Windows 10
  • WinAPI and C code

Question :

I have a buffer of pixel represented as uint8_t buffer[width * height * PIXEL_SIZE] that i would like to regularly modify the content data and redraw the buffer into the window.

I'm encoutering two issues with the winapi with which one i'm lost :

  • How do i print a buffer of pixel onto the window ?
  • How do i redraw later in the code the dame modifies buffer ?

I have made many researchs but no code snippet have successfully helped me to solve my issue.

Here is a not working code sample in order to show what i would like to archieve with the elements of code i have:

new_image.c

// Global variables
static HDC      hdc;
static HDC      context_hdc;
static HBITMAP  hDib;
static HGDIOBJ  obj;

static void     set_bmi_object(BITMAPINFO *bmi, int width, int height) {
    memset(bmi, 0, sizeof(BITMAPINFO));

    bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi->bmiHeader.biWidth = width;
    bmi->bmiHeader.biHeight = -height;
    bmi->bmiHeader.biPlanes = 1;
    bmi->bmiHeader.biBitCount = 32;
    bmi->bmiHeader.biCompression = BI_RGB;
}

// Allocate a new image buffer
void             *new_image(HWND hwnd, int width, int height)
{
    BITMAPINFO  bmi;
    BYTE        *bits = NULL;
    void        *buffer;

    if (NULL == (buffer = (char*)malloc(width * height * PIXEL_SIZE)))
        return (NULL);

    set_bmi_object(&bmi, width, height);

    hdc = GetDC(hwnd);

    hDib = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)(&bits), 
                            NULL, 0);

    if (hDib != NULL) {
        context_hdc = CreateCompatibleDC(hdc);
        if (context_hdc == NULL) {
            DeleteObject(hDib);
        } else {
            obj = SelectObject(context_hdc, hDib);
            CopyMemory(bits, buffer, width * height * sizeof(PIXEL_SIZE));
        }
    }

    return (newimg);
}

// Print the buffer of pixel on the window
void             put_image_to_window(HWND hwnd, void *buffer, int x, int y)
{
    (void)hwnd;

    // Void buffer because i should use directly HDCcontext_hdc linked to HGDIOBJ   obj ?
    (void)buffer;

    BitBlt(hdc, // destination
        x,
        y,
        500, // width of the region
        500, // height
        context_hdc, // source
        0,   // x
        0,   // y
        SRCCOPY);

    UpdateWindow(hwnd);
}

main.c

static const char g_szClassName[] = "myWindowClass";

static void paint(HWND hwnd) {
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);
    EndPaint(hwnd, &ps);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_PAINT:
        paint(hwnd);
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int main(void) {
    MSG             Msg;
    HINSTANCE       hInstance;
    HWND            hwnd;
    STARTUPINFOA    startup_info;
    WNDCLASSEX      wc;
    HWND            hwnd;

    GetStartupInfoA(&startup_info);

    hInstance = GetModuleHandle(NULL);

    memset(&wc, 0, sizeof(wc));

    // Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    // ... etc
    wc.lpszClassName = TEXT(g_szClassName);

    if (!RegisterClassEx(&wc)) {
        return (-1);
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Title,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        500, 
        500,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (hwnd == NULL) {
        return (-1);
    }

    ShowWindow(hwnd, startup_info.wShowWindow);


    image = new_image(hwnd, 500, 500);

    put_image_to_window(hwnd, image, 0, 0);

    UpdateWindow(hwnd);

    // The Message Loop
    while (GetMessage(&Msg, NULL, 0, 0)) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return (Msg.wParam);
}

Here is in new_image.c

  • a function new_image that returns a pointer on a buffer of pixels,

  • a function put_image_to_window that display the buffer of pixels on the window.

user7364588
  • 1,014
  • 2
  • 12
  • 32
  • If UpdateWindow isn't working, you can probably trigger a redraw with [InvalidateRect](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-invalidaterect), and if your WM_PAINT handler can use it you can then be specific about the bit that's changed. – Rup Oct 11 '18 at 19:10
  • @Rup i think for the specific bytes that changed, you are talking about PAINTSTRUCT, i will document about it. But actually, the WM_PAINT event case do not use the buffer, i've seen some people use BitBlt in the WM_PAINT case, and other outside WndProc – user7364588 Oct 11 '18 at 19:13
  • 4
    You are drawing outside the `WM_PAINT` handler, so anything you draw will be overwritten at the next paint request. And your `WM_PAINT` handler draws nothing, so it will just draw your `wc.hbrBackground`. – Raymond Chen Oct 11 '18 at 19:19
  • @RaymondChen Thank you for your reponse ! So if every draw pass through WM_PAINT, does it mean that i cannot draw outside WM_PAINT handler like i am trying to do ? – user7364588 Oct 11 '18 at 19:25
  • 3
    Mandatory reading: [Painting and Drawing](https://learn.microsoft.com/en-us/windows/desktop/gdi/painting-and-drawing). Make sure to read *all* of it. – IInspectable Oct 11 '18 at 19:28
  • 2
    You have to be able to do all your drawing inside WM_PAINT anyway, because you need to regenerate your content if the user minimizes the window and then restores it. If you draw outside of WM_PAINT, you need to remember enough information so that WM_PAINT can replicate what you did. Most people just let WM_PAINT do all the drawing. Avoids code duplication. – Raymond Chen Oct 11 '18 at 20:52
  • 1
    There are two ways to handle this situation: 1) the "old" way by handling `WM_PAINT`, where the pixels are put into an `HBITMAP` that is then re-drawn onto the window's `HDC` every time `WM_PAINT` is received; 2) the "new" way using the `WS_EX_LAYERED` window style with the `UpdateLayeredWindow()` function. Put the pixels into an `HBITMAP` and create an in-memory `HDC` to select it into, then pass that `HDC` to `UpdateLayeredWindow()` and do not handle `WM_PAINT` at all. Whenever the pixels are changed, simply call `UpdateLayeredWindow()` again. – Remy Lebeau Oct 11 '18 at 21:27

2 Answers2

1
CopyMemory(bits, buffer, width * height * sizeof(PIXEL_SIZE));

Usage of sizeof operator is not correct. A bitmap can be 1, 4, 8, 16, 24, or 32 bit. A 32-bit bitmap has 4 bytes per pixel. If PIXEL_SIZE is declared as int32_t then by coincidence you get the right size. Otherwise use the correct formula to calculate size.

Also, there is no point in copying buffer to bits. You can use bits directly. bits will be valid as long as you have not destroyed hDib

hdc = GetDC(hwnd);

A call to GetDC should end with ReleaseDC, otherwise you may end up with resource leak. Windows device context is not meant to be stored as a constant. Use it as temporary value instead.

You also have declared HDC context_hdc as global variable. This is okay for memory device context, but it's not necessary. The only global variable you need is hDib and perhaps buffer

#include <windows.h>

static const char g_szClassName[] = "myWindowClass";
static HBITMAP  hDib;

BYTE *new_image(int width, int height)
{
    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(BITMAPINFO));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = -height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    BYTE *buffer;
    HDC hdc = GetDC(HWND_DESKTOP);
    hDib = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)(&buffer), NULL, 0);
    ReleaseDC(HWND_DESKTOP, hdc);
    return buffer;
}

static void paint(HWND hwnd) 
{
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if(hDib)
    {
        HDC context_hdc = CreateCompatibleDC(hdc);
        HGDIOBJ old_obj = SelectObject(context_hdc, hDib);
        BitBlt(hdc, 0, 0, 500, 500, context_hdc, 0, 0, SRCCOPY);
        SelectObject(context_hdc, old_obj);
        DeleteDC(context_hdc);
    }

    EndPaint(hwnd, &ps);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_PAINT:
        paint(hwnd);
        break;
    case WM_MOUSEMOVE:
    {
        if(hDib)
        {
            //draw something
            HDC context_hdc = CreateCompatibleDC(NULL);
            HGDIOBJ old_obj = SelectObject(context_hdc, hDib);

            SetDCBrushColor(context_hdc, RGB(255, 0, 0));
            int x = (int)(short)LOWORD(lParam);
            int y = (int)(short)HIWORD(lParam);
            RECT rc = { x, y, x + 10, y + 10};
            FillRect(context_hdc, &rc, (HBRUSH)GetStockObject(DC_BRUSH));

            SelectObject(context_hdc, old_obj);
            DeleteDC(context_hdc);

            InvalidateRect(hwnd, NULL, FALSE);
        }
        break;
    }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int main(void) {
    MSG             Msg;
    HINSTANCE       hInstance;
    HWND            hwnd;
    STARTUPINFOA    startup_info;
    WNDCLASSEX      wc;

    GetStartupInfoA(&startup_info);
    hInstance = GetModuleHandle(NULL);
    memset(&wc, 0, sizeof(wc));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszClassName = TEXT(g_szClassName);
    RegisterClassEx(&wc);

    hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, g_szClassName, "Title",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        500, 500, NULL, NULL, hInstance, NULL);

    BYTE* image = new_image(500, 500);

    ShowWindow(hwnd, SW_SHOW);//startup_info.wShowWindow);
    UpdateWindow(hwnd);
    while(GetMessage(&Msg, NULL, 0, 0)) 
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    if (hDib)
        DeleteObject(hDib);

    return (Msg.wParam);
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Thank you for your response. I tried from your example to write into `BYTE* image = new_image(500, 500);` just after the call this way : `for (size_t i = 0; i < 500 * 500; i++) { ((uint32_t*)image)[i] = 0xF0; }` but actually it is failling. The `BYTE* image` seems to not be allocated the size 500 * 500 by `CreateDIBSection` – user7364588 Oct 14 '18 at 12:49
0

Solution

Thanks to the answers from @RemyLebeau @IInspectable @Raymond Chen and @BarmakShemirani answers, here is a solution.

Now i successfully update the window according to buffer obtained with the CreateDIBSection() function, without passing through the WM_PAINT event.

I use the UpdateLayeredWindow() function to update the window's pixels.

Here is the solution code :

new_image.c

// global variables
static HBITMAP hDib;

static void     set_bmi_object(BITMAPINFO *bmi, int width, int height) {
    memset(bmi, 0, sizeof(BITMAPINFO));

    bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi->bmiHeader.biWidth = width;
    bmi->bmiHeader.biHeight = -height;
    bmi->bmiHeader.biPlanes = 1;
    bmi->bmiHeader.biBitCount = 32;
    bmi->bmiHeader.biCompression = BI_RGB;
}

// Allocate a new image buffer
void             *new_image(HWND hwnd, int width, int height)
{
    BITMAPINFO  bmi;
    void        *buffer;
    HDC         hdc;

    set_bmi_object(&bmi, width, height);

    hdc = GetDC(hwnd);

    hDib = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)(&buffer), NULL, 0);

    ReleaseDC(instance->win_list->hwnd, hdc);

    return (buffer);
}

// Print the buffer of pixel on the window
void             put_image_to_window(HWND hwnd, void *buffer, int x, int y)
{
    HDC                     hdc;
    HDC                     context_hdc;
    HGDIOBJ                 old_obj;

    hdc = GetDC(hwnd);

    context_hdc = CreateCompatibleDC(hdc);

    old_obj = SelectObject(context_hdc, hDib);

    BitBlt(hdc,
        0,
        0,
       500,
       500,
       context_hdc,
       0,
       0,
       SRCCOPY);

    SelectObject(context_hdc, old_obj);

    DeleteDC(context_hdc);
    ReleaseDC(hwnd, hdc);

    // Call UpdateLayeredWindow
    BLENDFUNCTION blend = {0};
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 128;// half transparent
    blend.AlphaFormat = AC_SRC_ALPHA;
    POINT ptLocation = {x, y};
    SIZE szWnd = {500, 500};
    POINT ptSrc = {0, 0};
    UpdateLayeredWindow(hwnd, hdc, &ptLocation, &szWnd, context_hdc, &ptSrc, 0, &blend, ULW_ALPHA);
}

main.c

static const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

int main(void) {
    MSG             Msg;
    HINSTANCE       hInstance;
    HWND            hwnd;
    STARTUPINFOA    startup_info;
    WNDCLASSEX      wc;
    HWND            hwnd;

    GetStartupInfoA(&startup_info);

    hInstance = GetModuleHandle(NULL);

    memset(&wc, 0, sizeof(wc));

    // Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    // ... etc
    wc.lpszClassName = TEXT(g_szClassName);

    if (!RegisterClassEx(&wc)) {
        return (-1);
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Title,
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        500, 
        500,
        NULL,
        NULL,
        hInstance,
        NULL);

    if (hwnd == NULL) {
        return (-1);
    }

    ShowWindow(hwnd, startup_info.wShowWindow);

    image = new_image(hwnd, 500, 500);

    put_image_to_window(hwnd, image, 0, 0);

    // The Message Loop
    while (GetMessage(&Msg, NULL, 0, 0)) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return (Msg.wParam);
}

Here is a must read that @IInspectable gave, for WinAPI beginners like me : Painting and Drawing.

Should have read this before questionning ...

user7364588
  • 1,014
  • 2
  • 12
  • 32