1

I am using the Windows API to draw pixels to the screen directly (using CPU, not GPU) and am running into a problem either with the way I am loading bitmaps, or rendering them. Here is the relevant code (the render_bmp function takes an argument "buffer" that is a pointer to a win32_offscreen_buffer, this is the global buffer that is displayed in the window)

Here is a screenshot of what the image looks like in my program:

2 of hearts

And here is the source bitmap:

enter image description here

NOTE: This does compile with the cl compiler on Windows using the following command, assuming you have the user32.lib and gdi32.lib installed:

cl -FC -Zi C:\stuff\reproduce.cpp user32.lib gdi32.lib

Here is the minimum code needed to reproduce the problem. You would need to replace the char *filename assignment in WinMain with whatever path to a .bmp on your machine to reproduce.

#include <windows.h>

struct win32_offscreen_buffer {
    BITMAPINFO info;
    void *memory;
    int width;
    int height;
    int bytes_per_pixel;
    int pitch;
};

struct window_dimension {
    int width;
    int height;
};

struct read_file_result {
    unsigned int contents_size;
    void *contents;
};

struct bitmap_result {
    BITMAPFILEHEADER *file_header;
    BITMAPINFOHEADER *info_header;
    unsigned int *pixels;
    unsigned int stride;
};

win32_offscreen_buffer global_buffer;
unsigned char should_quit = 0;  // BOOL

window_dimension get_window_dimension(HWND window) {
    RECT client_rect;
    GetClientRect(window, &client_rect);
    
    window_dimension result;
    
    result.width = client_rect.right - client_rect.left;
    result.height = client_rect.bottom - client_rect.top;
    
    return result;
}

void resize_dib_section(win32_offscreen_buffer* buffer, int width, int height) {
    if (buffer->memory) {
        VirtualFree(buffer->memory, 0, MEM_RELEASE);
    }
    
    int bytes_per_pixel = 4;
    
    buffer->width = width;
    buffer->height = height;
    
    buffer->info.bmiHeader.biSize = sizeof(buffer->info.bmiHeader);
    buffer->info.bmiHeader.biWidth = buffer->width;
    buffer->info.bmiHeader.biHeight = -buffer->height;
    buffer->info.bmiHeader.biPlanes = 1;
    buffer->info.bmiHeader.biBitCount = 32;
    buffer->info.bmiHeader.biCompression = BI_RGB;
    
    int bitmap_memory_size = (buffer->width * buffer->height) * bytes_per_pixel;
    buffer->memory = VirtualAlloc(0, bitmap_memory_size, MEM_COMMIT, PAGE_READWRITE);
    
    buffer->pitch = buffer->width * bytes_per_pixel;
    buffer->bytes_per_pixel = bytes_per_pixel;
}

void display_buffer_in_window(HDC device_context, window_dimension dimension) {
    StretchDIBits(device_context,
                  0, 0, dimension.width, dimension.height,
                  0, 0, global_buffer.width, global_buffer.height,
                  global_buffer.memory,
                  &global_buffer.info,
                  DIB_RGB_COLORS, SRCCOPY);
}

void free_file_memory(void *memory) {
    if (memory) {
        VirtualFree(memory, 0, MEM_RELEASE);
    }
}

read_file_result read_entire_file(LPCSTR filename) {
    read_file_result result = {};
    
    HANDLE file_handle = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    
    if (file_handle != INVALID_HANDLE_VALUE) {
        LARGE_INTEGER file_size;
        if(GetFileSizeEx(file_handle, &file_size)) {
            unsigned int file_size32 = file_size.QuadPart;
            result.contents = VirtualAlloc(0, file_size32, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
            
            if (result.contents) {
                DWORD bytes_read;
                if (ReadFile(file_handle, result.contents, file_size32, &bytes_read, 0) && (file_size32 == bytes_read)) {
                    // File read successfully.
                    result.contents_size = file_size32;
                } else {
                    // TODO: Logging
                    free_file_memory(result.contents);
                    result.contents = 0;
                }
            } else {
                // TODO: Logging
            }
        } else {
            // TODO: Logging
        }
        
        CloseHandle(file_handle);
    } else {
        // TODO: Logging
    }
    
    return result;
}

bitmap_result debug_load_bitmap(char* filename) {
    bitmap_result bmp_result = {};

    read_file_result file_result = read_entire_file(filename);
    unsigned char *contents = (unsigned char *)file_result.contents;

    bmp_result.file_header = (BITMAPFILEHEADER *)contents;
    bmp_result.info_header = (BITMAPINFOHEADER *)(contents + 14);
    bmp_result.pixels = (unsigned int *)(contents + bmp_result.file_header->bfOffBits);
    bmp_result.stride = ((((bmp_result.info_header->biWidth * bmp_result.info_header->biBitCount) + 31) & ~31) >> 3);

    return bmp_result;
}

void render_bmp(int x_pos, int y_pos, win32_offscreen_buffer *buffer, bitmap_result bmp) {
    int width = bmp.info_header->biWidth;
    int height = bmp.info_header->biHeight;

    unsigned char* dest_row = (unsigned char*)buffer->memory + (y_pos * buffer->pitch + x_pos);

    // NOTE: Doing this calculation on the source row because the bitmaps are bottom up,
    // whereas the window is top-down. So must start at the bottom of the source bitmap,
    // working left to right.
    unsigned char* source_row = (unsigned char*)(bmp.pixels + ((bmp.stride / 4) * (height - 1)));

    for (int y = y_pos; y < y_pos + height; y++) {
        unsigned int* dest = (unsigned int*)dest_row;
        unsigned int* source = (unsigned int*)source_row;

        for (int x = x_pos; x < x_pos + width; x++) {
            *dest = *source;
            dest++;
            source++;
        }

        dest_row += buffer->pitch;
        source_row -= bmp.stride;
    }
}

LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM w_param, LPARAM l_param) {
    LRESULT result = 0;

    switch (message) {
        break;
        case WM_SIZE: {
            window_dimension dim = get_window_dimension(window);
            resize_dib_section(&global_buffer, dim.width, dim.height);
        }
        break;

        case WM_CLOSE: {
            OutputDebugStringA("WM_CLOSE\n");
            should_quit = 1;
        }
        break;

        case WM_ACTIVATEAPP: {
            OutputDebugStringA("WM_ACTIVATEAPP\n");
        }
        break;

        case WM_DESTROY: {
            OutputDebugStringA("WM_DESTROY\n");
        }
        break;

        case WM_PAINT: {
            PAINTSTRUCT paint;
            HDC device_context = BeginPaint(window, &paint);
            
            window_dimension dimension = get_window_dimension(window);
            display_buffer_in_window(device_context, dimension);
            
            OutputDebugStringA("WM_PAINT\n");

            EndPaint(window, &paint);
        }
        break;

        default: {
            result = DefWindowProc(window, message, w_param, l_param);
        }
        break;
    }

    return result;
}

int CALLBACK WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR command_line, int show_code) {
    WNDCLASS window_class = {};

    window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    window_class.lpfnWndProc = window_proc;
    window_class.hInstance = instance;
    window_class.lpszClassName = "PokerWindowClass";
    
    if (RegisterClassA(&window_class)) {
        HWND window_handle = CreateWindowExA(0, window_class.lpszClassName, "Poker",
                                      WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
                                      CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, instance, 0);

        if (window_handle) {
            HDC device_context = GetDC(window_handle);
            
            window_dimension dim = get_window_dimension(window_handle);
            resize_dib_section(&global_buffer, dim.width, dim.height);
            
            char *filename = "c:/stuff/Sierpinski.bmp";
            bitmap_result img = debug_load_bitmap(filename);
            
            // MESSAGE LOOP
            while (!should_quit) {
                MSG msg;
                
                while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                    if (msg.message == WM_QUIT) {
                        should_quit = 1;
                    }
                    
                    TranslateMessage(&msg);
                    DispatchMessageA(&msg);
                }
                
                render_bmp(0, 0, &global_buffer, img);
                
                window_dimension dimension = get_window_dimension(window_handle);
                display_buffer_in_window(device_context, dimension);
            }
        } else {
            OutputDebugStringA("ERROR: Unable to create window.");
        }
    } else {
        OutputDebugStringA("ERROR: Unable to register the window class.");
    }
}

Note that the biBitCount of the source bitmap is 24, whereas the offscreen buffer bitmap has a biBitCount of 32.

6
  • Unrelated: #include "poker.cpp" is likely a bad idea. Since you seem to have an executable program it didn't bite you today, but including .cpp files can break builds in a number of different ways.See Why should I not include cpp files and instead use a header? for more on the topic. Commented Jun 3, 2020 at 17:49
  • 1
    That's way too much code to dig through, you should try to make a minimal example. Offhand I'd guess you're not using a RGB bitmap, you're using a paletted bitmap. Oops never mind, the screen shot at the end verifies that it's 24 bits. Commented Jun 3, 2020 at 17:49
  • @MarkRansom I removed a bunch of code I assumed was irrelevant. Any ideas/possible pathways of inquisition? I'm stumped. Commented Jun 5, 2020 at 0:42
  • @JacksonLenhart This is not a complete and reproducible sample. safe_truncate_uint64 and global_buffer definitions are missed. Could you show how to reproduce this issue? Commented Jun 5, 2020 at 7:04
  • @RitaHan-MSFT I edited to include the definitions of safe_truncate_uint64 and global_buffer. However it is still not complete, if you want to try and reproduce the issue, which would be greatly appreciated, here is the repository on github (it is quite small): github.com/jackson-lenhart/native_poker Commented Jun 12, 2020 at 2:56

1 Answer 1

0

For drawing a bitmap, there are more simple methods. You don't have to parse the .bmp file by yourself. Give two samples as below you can have a try.

First method using LoadImage function. Refer to "How to draw image on a window?"

case WM_CREATE:
{
    hBitmap = (HBITMAP)LoadImage(hInst, L"C:\\projects\\native_poker\\card-BMPs\\c08.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
}
break;
case WM_PAINT:
{
    PAINTSTRUCT     ps;
    HDC             hdc;
    BITMAP          bitmap;
    HDC             hdcMem;
    HGDIOBJ         oldBitmap;

    hdc = BeginPaint(hWnd, &ps);

    hdcMem = CreateCompatibleDC(hdc);
    oldBitmap = SelectObject(hdcMem, hBitmap);

    GetObject(hBitmap, sizeof(bitmap), &bitmap);
    BitBlt(hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);

    SelectObject(hdcMem, oldBitmap);
    DeleteDC(hdcMem);

    EndPaint(hWnd, &ps);
}
break;

Second method using GDI+. Refer to "Loading and Displaying Bitmaps".

#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

//...

VOID OnPaint(HDC hdc)
{
    Graphics graphics(hdc);
    Image *image = Image::FromFile(L"C:\\projects\\native_poker\\card-BMPs\\c08.bmp");
    graphics.DrawImage(image, 10, 10);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        OnPaint(hdc);
        EndPaint(hWnd, &ps);
    }
    break;

//...

}
Sign up to request clarification or add additional context in comments.

1 Comment

I am doing a DIY approach with this so I can have full control over the asset loading and rendering code rather than using Windows library functions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.