将原始像素数组绘制到窗口上(C++ WinAPI)

问题描述 投票:0回答:2

我有两个缓冲区,大小和类型都相同(

uint32_t*
)。 这些缓冲区应该代表渲染/绘图系统的前缓冲区和后缓冲区。它们存储 32 位像素数据。

我使用;

声明并初始化这些缓冲区
private:
    ...
    uint32_t* frontBuf;
    uint32_t* backBuf;
    size_t    gbufSize;
    ...
public:
    void Initialize() {
        ...  
        // prepare for rendering
        gbufSize = sizeof(uint32_t) * w * h;
        backBuf  = (uint32_t*)malloc(gbufSize);
        frontBuf = (uint32_t*)malloc(gbufSize);
        ...
    }

我还使用以下方法在

Initialize()
方法中获取控制台的输出、窗口和设备句柄:

        // get handles
        cwd = GetDC(GetConsoleWindow());
        chd = GetStdHandle(STD_OUTPUT_HANDLE);
        cwn = GetConsoleWindow();

然后我有一些绘图方法,例如

SetPixel
方法:

    size_t GFlatten(int x, int y) {
        return y * h + x;
    }

    void GSetPixel(int x, int y, uint32_t color) {
        backBuf[GFlatten(x, y)] = color;
    }

最后,我有一个

GSwap
方法。此方法应该交换缓冲区的指针,清除新的后台缓冲区并将前台缓冲区复制到屏幕。

前两个有效(我认为),但我不知道如何实现第三个(复制到屏幕)。我不想使用任何外部库。

GSwap
方法代码:

    void GSwap() {
        // swap pointers
        uint32_t* frontTmp = frontBuf;
        frontBuf = backBuf;
        backBuf = frontTmp;

        // clear new back buffer
        memset(backBuf, 0, gbufSize);

        // draw on screen
        /* ??? */
    }

完整代码:Pastebin

c++ winapi graphics buffer drawing
2个回答
7
投票

您无法通过将字节插入某个缓冲区来绘制任意窗口。 Win32 是比它更高级别的 API。

但是,可以通过写入缓冲区来将 绘制为位图。您创建一个位图,以便在您使用

CreateDIBSection(...)
创建位图时 Windows 返回指向其内容的指针。然后,您可以通过
BitBlt
和适当的设备上下文等将该位图绘制到窗口。

下面是一个最小的例子。 (我保留了后台缓冲区和前台缓冲区的使用,尽管这里实际上并不需要。窗口本身本质上是一个前台缓冲区,因此您只需要一个后台缓冲区的“交换链”即可避免闪烁。 )

#include <windows.h>
#include <stdint.h>
#include <utility>
#include <algorithm>

constexpr int kTimerID = 101;

LRESULT CALLBACK wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

struct graphics_buffer {
    HBITMAP hbm;
    uint32_t* data;
};

graphics_buffer create_graphics_buffer(int wd, int hgt)
{
    HDC hdcScreen = GetDC(NULL);

    BITMAPINFO bmi = {};
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = wd;
    bmi.bmiHeader.biHeight = -hgt; // top-down
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    graphics_buffer gb;
    gb.hbm = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, reinterpret_cast<void**>(&gb.data), NULL, NULL);

    ReleaseDC(NULL, hdcScreen);
    return gb;
}

class graphic_buffers {
    graphics_buffer front_;
    graphics_buffer back_;
    int wd_; 
    int hgt_;

public:

    graphic_buffers(int wd, int hgt) :
        wd_(wd),
        hgt_(hgt),
        front_(create_graphics_buffer(wd, hgt)),
        back_(create_graphics_buffer(wd, hgt))
    {
        clear();
    }

    HBITMAP front_bmp() {
        return front_.hbm;
    }

    void swap() {
        std::swap(front_, back_);
    }

    size_t size() const {
        return static_cast<size_t>(wd_ * hgt_);
    }

    int width() const {
        return wd_;
    }

    int height() const {
        return hgt_;
    }

    void clear() {
        std::fill(back_.data, back_.data + size(), 0);
    }

    void set_pixel(int x, int y, uint32_t pix) {
        back_.data[y * wd_ + x] = pix;
    }

    ~graphic_buffers() {
        DeleteObject(front_.hbm);
        DeleteObject(back_.hbm);
    }
};

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

    MSG msg = { 0 };
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = wndproc;
    wc.hInstance = hInstance;
    wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszClassName = L"swap_buffers_window";
    if (!RegisterClass(&wc))
        return 1;

    if (!CreateWindow(wc.lpszClassName,
        L"buffered window",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        0, 0, 640, 480, 0, 0, hInstance, NULL))
        return 2;

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

void draw_something(graphic_buffers& buffs) {
    int wd = buffs.width();
    int hgt = buffs.height();

    static int x = 0;
    static int y = 0;
    static int x_vel = 4;
    static int y_vel = 7;

    if (x >= 0 && x < wd && y >= 0 && y < hgt) {
        buffs.set_pixel(x, y, 0xffffffff);
    }

    x += x_vel;
    y += y_vel;
    if (x < 0 || x > wd) {
        x_vel *= -1;
    }
    if (y < 0 || y > hgt) {
        y_vel *= -1;
    }
}

LRESULT CALLBACK wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)
    {
    case WM_CREATE: {
            RECT r;
            GetClientRect(hWnd, &r);
            SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG>(new graphic_buffers(r.right - r.left, r.bottom - r.top)));
            SetTimer(hWnd, kTimerID, 1, NULL);
        }
        break;
    case WM_TIMER: {
            auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
            draw_something(*buffs);
            buffs->swap();
            buffs->clear();
            InvalidateRect(hWnd, NULL, FALSE);
        }
        break;
    case WM_PAINT: {
            auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            HDC hdc_bmp = CreateCompatibleDC(hdc);
            auto old_bmp = SelectObject(hdc_bmp, buffs->front_bmp());

            BitBlt(hdc, 0, 0, buffs->width(), buffs->height(), hdc_bmp, 0, 0, SRCCOPY);

            SelectObject(hdc, old_bmp);
            DeleteDC(hdc_bmp);
            EndPaint(hWnd, &ps);
        }
        break;

    case WM_DESTROY: {
            auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
            delete buffs;
        }
        break;

    case WM_CLOSE:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

0
投票

至少在我的平台(win11/64bit)上,@jwezorek 的出色回复需要更新为:

SetWindowLongPtr(hWnd,GWLP_USERDATA,reinterpret_cast(...));

(即 LONG->LONG_PTR)以避免崩溃。我发布此注释作为答案,因为我无法发表评论。

© www.soinside.com 2019 - 2024. All rights reserved.