【问题标题】:Array buffer and winapi数组缓冲区和winapi
【发布时间】:2019-03-16 23:01:00
【问题描述】:

上下文:

  • Windows 10
  • WinAPI 和 C 代码

问题:

我有一个像素缓冲区,表示为uint8_t buffer[width * height * PIXEL_SIZE],我想定期修改内容数据并将缓冲区重绘到窗口中。

我遇到了两个与我迷路的 winapi 相关的问题:

  • 如何在窗口上打印像素缓冲区?
  • 我如何在后面的代码中重绘女士修改缓冲区?

我做了很多研究,但没有代码 sn-p 成功地帮助我解决了我的问题。

这里是一个不工作的代码示例,以展示我想用我拥有的代码元素归档的内容:

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);
}

这里是new_image.c

  • 一个函数new_image,它返回一个像素缓冲区的指针,

  • 一个函数put_image_to_window在窗口上显示像素缓冲区。

【问题讨论】:

  • 如果 UpdateWindow 不起作用,您可能会使用InvalidateRect 触发重绘,如果您的 WM_PAINT 处理程序可以使用它,那么您可以具体说明更改的位。
  • 您在WM_PAINT 处理程序之外进行绘制,因此您绘制的任何内容都将在下一次绘制请求时被覆盖。而你的WM_PAINT 处理程序什么也没画,所以它只会画你的wc.hbrBackground
  • 必读:Painting and Drawing。请务必阅读全部
  • 无论如何,您必须能够在 WM_PAINT 中完成所有绘图,因为如果用户最小化窗口然后恢复它,您需要重新生成您的内容。如果您在 WM_PAINT 之外绘制,则需要记住足够的信息,以便 WM_PAINT 可以复制您所做的。大多数人只是让 WM_PAINT 完成所有的绘图。避免代码重复。
  • 有两种处理这种情况的方法:1)处理WM_PAINT的“旧”方法,其中像素被放入HBITMAP,然后重新绘制到窗口的@ 987654334@每次收到WM_PAINT; 2) 使用WS_EX_LAYERED 窗口样式和UpdateLayeredWindow() 函数的“新”方式。将像素放入HBITMAP 并创建内存中的HDC 以将其选中,然后将HDC 传递给UpdateLayeredWindow() 并且根本不处理WM_PAINT。每当像素发生变化时,只需再次调用UpdateLayeredWindow()

标签: c windows winapi pixmap


【解决方案1】:
CopyMemory(bits, buffer, width * height * sizeof(PIXEL_SIZE));

sizeof 运算符的使用不正确。位图可以是 1、4、8、16、24 或 32 位。 32 位位图每个像素有 4 个字节。如果PIXEL_SIZE 被声明为int32_t,那么碰巧你得到了正确的大小。否则使用正确的公式计算大小。

另外,将buffer 复制到bits 是没有意义的。您可以直接使用bits。只要您没有销毁hDibbits 就会有效

hdc = GetDC(hwnd);

GetDC 的调用应以ReleaseDC 结尾,否则可能会导致资源泄漏。 Windows 设备上下文并不意味着存储为常量。将其用作临时值。

您还已将HDC context_hdc 声明为全局变量。这对于内存设备上下文是可以的,但这不是必需的。您需要的唯一全局变量是 hDib 可能还有 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);
}

【讨论】:

  • 感谢您的回复。我尝试在您的示例中以这种方式在调用后写入BYTE* image = new_image(500, 500);for (size_t i = 0; i &lt; 500 * 500; i++) { ((uint32_t*)image)[i] = 0xF0; },但实际上它失败了。 BYTE* image 似乎没有被 CreateDIBSection 分配大小 500 * 500
【解决方案2】:

解决方案

感谢@RemyLebeau @IInspectable @Raymond Chen 和@BarmakShemirani 的回答,这是一个解决方案。

现在我根据CreateDIBSection()函数获得的缓冲区成功更新了窗口,没有通过WM_PAINT事件。

我使用UpdateLayeredWindow() 函数来更新窗口的像素。

这里是解决方案代码:

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);
}

这是@IInspectable 为像我这样的 WinAPI 初学者提供的必读:Painting and Drawing

在提问之前应该阅读此内容......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-23
    • 1970-01-01
    • 2018-11-17
    • 2013-12-10
    • 2014-01-29
    • 2013-07-19
    • 2017-08-06
    • 2021-01-11
    相关资源
    最近更新 更多