如何使用Win32/GDI加载PNG图像(尽可能不使用GDI +)?

31

是否可以使用Win32 GDI函数从文件中加载PNG格式的图片到HBITMAP中?如果不能,那么最轻量级的解决方案是什么,而不需要使用外部库(如libpng)?


2
我只是尽可能地想保持一切小而快。我以前在GDI+的速度方面有过不好的经历。我需要一个HBITMAP,而GDI+不能直接加载到HBITMAP中,因此需要另一个副本。GDI+是一种选择,但不是我最喜欢的。 - jnm2
5个回答

30

1
嗨,Bradley,看起来 MSDN 杂志的链接挂了,你能更新一下链接并且在文章答案中加入一些示例代码吗?谢谢! - jrh
3
是的,看起来微软已经删除了那个页面,但是可以在存档中找到:http://web.archive.org/web/20080507014245/http://msdn.microsoft.com/en-us/magazine/cc500647.aspx - Bradley Grainger
1
请注意,微软已将旧问题转换为chm格式 - 如果您愿意,我可以建议进行编辑,将代码添加到答案中。 - jrh

10

不需要使用Windows Imaging Component、GDI+或PNG库。您可以使用图标功能。

  1. 在VC项目资源中添加新的图标(ICO_PNG),设置自定义宽度和高度(资源编辑器- > 图像 - > 新图像类型)。在此处复制您的png图像并使用填充工具+透明颜色使图标透明。

  2. 将图片控件(IDC_PNG)添加到您的对话框(类型=所有者绘制)。

  3. 对话框过程代码:

switch (msg)
{
    ...

    case WM_DRAWITEM:
    {
        LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lParam;
        if (pDIS->CtlID == IDC_PNG)
        {
            HICON hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(ICO_LOGO), IMAGE_ICON, 0, 0, LR_LOADTRANSPARENT); 
            DrawIconEx(pDIS->hDC, 0, 0, hIcon, 0, 0, 0, NULL, DI_NORMAL);
            DestroyIcon(hIcon);
            return TRUE;
        }
    }
}

相关:如何使用LoadImage和StretchDIBits绘制PNG图像?。请注意,此答案似乎仅适用于png是资源的情况,我无法直接从独立文件(例如,myimage.png)加载png。似乎LoadImage仅支持从文件加载.ico图标。 - jrh
1
你确定LoadImage可以加载PNG资源吗?我没有创建IDC,只需要加载资源,但似乎它没有加载。 - Sergei Krivonos

4

经过多次撞头后,我很高兴终于看到了这个答案! :) - user541686
3
哎呀!我做了一些研究。看起来没有可以绘制PNG图像到GDI设备上下文的有效代码。许多人指出StretchDIBits对PNG支持完全无用。 - 9dan

0

我们可以通过GDI显示png图像,当创建窗口时(在窗口过程函数中的WM_CREATE情况下),按照以下两个步骤:

  1. 加载png文件(通过libpng或stb image),将像素值保存在一个变量中,比如说buffer
  2. 使用CreateBitmap()函数中的buffer创建HBITMAP实例

这是可运行的代码,它是纯C和main()作为入口点函数(libpng和zlib来自我的opencv编译)

#include <stdio.h>
#include <windows.h>
#include "png.h"


#define CRTDBG_MAP_ALLOC 
#include <crtdbg.h>

// **NB**: You may use OpenCV prebuilt package's self contained libpng.lib file
// or, maybe, you can also compile it from source (which cost time and not necessary), see: `http://www.libpng.org` and  `https://www.zlib.net`

#define LIBPNG_PTH "D:/opencv_249/build/x64/vc12/staticlib/libpng.lib"
#define ZLIB_PTH "D:/opencv_249/build/x64/vc12/staticlib/zlib.lib"

#pragma comment(lib, LIBPNG_PTH)
#pragma comment(lib, ZLIB_PTH)

typedef struct MyRect {
    int x, y, width, height;
} MyRect;

char bitmap_im_pth[100];

typedef struct MyWindow {
    HDC dc;
    //HGDIOBJ image;
    HBITMAP hBmp;
    unsigned char* imdata;
} MyWindow;

MyWindow* my_window;
enum ImageType {BMP, PNG};

long ReadPngData(const char *szPath, int *pnWidth, int *pnHeight, unsigned char **cbData)
{
    FILE *fp = NULL;
    long file_size = 0, pos = 0, mPos = 0;
    int color_type = 0, x = 0, y = 0, block_size = 0;

    png_infop info_ptr;
    png_structp png_ptr;
    png_bytep *row_point = NULL;

    fp = fopen(szPath, "rb");
    if (!fp)    return -1;

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    info_ptr = png_create_info_struct(png_ptr);
    png_init_io(png_ptr, fp);
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);

    *pnWidth = png_get_image_width(png_ptr, info_ptr);
    *pnHeight = png_get_image_height(png_ptr, info_ptr);
    color_type = png_get_color_type(png_ptr, info_ptr);
    file_size = (*pnWidth) * (*pnHeight) * 4;
    *cbData = (unsigned char *)malloc(file_size);
    row_point = png_get_rows(png_ptr, info_ptr);

    block_size = color_type == 6 ? 4 : 3;

    for (x = 0; x < *pnHeight; x++)
        for (y = 0; y < *pnWidth*block_size; y += block_size)
        {
            (*cbData)[pos++] = row_point[x][y + 2];        //B
            (*cbData)[pos++] = row_point[x][y + 1];        //G
            (*cbData)[pos++] = row_point[x][y + 0];        //R
            (*cbData)[pos++] = row_point[x][y + 3];        //alpha
        }

    png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    fclose(fp);

    return file_size;
}


LRESULT __stdcall WindowProcedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
{
    int im_width, im_height;

    int image_type = PNG;
    switch (msg)
    {
    case WM_CREATE:
        if (image_type == BMP) {
            my_window->hBmp = (HBITMAP)LoadImage(NULL, "lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
        }
        else if (image_type == PNG) {
            ReadPngData("Lena.png", &im_width, &im_height, &my_window->imdata);
            my_window->hBmp = CreateBitmap(im_width, im_height, 32, 1, my_window->imdata);
        }
        if (my_window->hBmp == NULL)
            MessageBox(window, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION);
        break;

    case WM_PAINT:
    {
        BITMAP bm;
        PAINTSTRUCT ps;

        HDC hdc = BeginPaint(window, &ps);
        SetStretchBltMode(hdc, COLORONCOLOR);

        my_window->dc = CreateCompatibleDC(hdc);
        HBITMAP hbmOld = SelectObject(my_window->dc, my_window->hBmp);

        GetObject(my_window->hBmp, sizeof(bm), &bm);

#if 1
        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, SRCCOPY);
#else
        RECT rcClient;
        GetClientRect(window, &rcClient);
        int nWidth = rcClient.right - rcClient.left;
        int nHeight = rcClient.bottom - rcClient.top;
        StretchBlt(hdc, 0, 0, nWidth, nHeight, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
#endif

        SelectObject(my_window->dc, hbmOld);
        DeleteDC(my_window->dc);

        EndPaint(window, &ps);
    }
    break;

    case WM_DESTROY:
        printf("\ndestroying window\n");
        PostQuitMessage(0);
        return 0L;

    case WM_LBUTTONDOWN:
        printf("\nmouse left button down at (%d, %d)\n", LOWORD(lp), HIWORD(lp));

        // fall thru
    default:
        //printf(".");
        return DefWindowProc(window, msg, wp, lp);
    }
}

const char* szWindowClass = "myclass";


ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    /* Win 3.x */
    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = WindowProcedure;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = GetModuleHandle(0);
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = 0;
    wc.lpszClassName = szWindowClass;
    /* Win 4.0 */
    wc.hIconSm = LoadIcon(0, IDI_APPLICATION);

    return RegisterClassEx(&wc);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    MyRect rect;
    rect.x = 300;
    rect.y = 300;
    rect.width = 640;
    rect.height = 480;

    DWORD defStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;

    HWND hwnd = CreateWindowEx(0, szWindowClass, "title",
        defStyle, rect.x, rect.y,
        rect.width, rect.height, 0, 0, hInstance, 0);

    if (!hwnd)
    {
        return FALSE;
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    return TRUE;
}

void create_my_window(MyWindow** _my_window) {
    MyWindow* my_window = (MyWindow*)malloc(sizeof(MyWindow));
    my_window->dc = NULL;
    my_window->imdata = NULL;
    my_window->hBmp = NULL;

    *_my_window = my_window; // write back
}

void destroy_my_window(MyWindow* my_window) {
    if (my_window) {
        if (my_window->imdata) free(my_window->imdata);
        free(my_window);
    }
}

int main()
{
    printf("hello world!\n");

    HINSTANCE hInstance = GetModuleHandle(0);
    int nCmdShow = SW_SHOWDEFAULT;

    MyRegisterClass(hInstance);
    create_my_window(&my_window);

    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0)) {
        DispatchMessage(&msg);
    }

    destroy_my_window(my_window);


    return 0;

}

参考资料:https://www.cnblogs.com/mr-wid/archive/2013/04/22/3034840.html


请不要使用硬编码路径。它们在其他人的电脑上无法工作。并非所有人都有“D:”... #pragma comment(lib, "D:/opencv345/mybuild/libpng.lib") - 我已相应地编辑了您的答案。 - Michael Haephrati
@MichaelHaephrati 很抱歉,我不太同意您的观点。我将libpng.lib的具体路径粘贴为“F:\ zhangzhuo \ lib \ opencv_249 \ build \ x64 \ vc12 \ staticlib \ libpng.lib”,是为了告诉人们他们可以使用opencv 249预构建包中的自包含库。人们很容易获得预构建的opencv版本,因此他们不必手动编译zlib和libpng,这是耗时的。当然,人们应该修改该路径,因为人们可能有不同的路径。 - ChrisZZ
2
好的。我明白。 - Michael Haephrati
1
问题在询问如何仅使用操作系统提供的服务(Win32 / GDI)加载PNG图像。建议使用某些随意的第三方库不回答该问题。该问题甚至明确说明“不使用外部库(例如libpng)”。这并不有用。 - IInspectable

-3

vladimir_hr的答案是非常简单明了的。

遵循以下简单步骤。

在资源头文件中声明: #define IDI_PNG 1000

在资源文件*.rc中有: IDI_PNG ICON "protractor.ico"

图标文件。 使用支持自定义大小而不是标准Windows图标大小的图标编辑器将(透明)png文件转换为图标文件,将此png图像保存为图标图像。

其余的就是在DC之间进行blitting。


1
如果您正在引用另一个答案,请在该答案下面写一条评论。 - EFrank
1
问题是如何加载PNG图像。建议使用不同的图像格式并不能回答这个问题。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接