如何使用 Win32/GDI 加载 PNG 图像(如果可能,不要使用 GDI+)?
Posted
技术标签:
【中文标题】如何使用 Win32/GDI 加载 PNG 图像(如果可能,不要使用 GDI+)?【英文标题】:How would I load a PNG image using Win32/GDI (no GDI+ if possible)? 【发布时间】:2011-06-01 20:36:10 【问题描述】:是否可以使用 Win32 GDI 函数将 PNG 从文件加载到 HBITMAP 中?如果没有,不使用外部库(如 libpng)的最轻量级解决方案是什么?
【问题讨论】:
我只是想让一切都尽可能小而快。过去我对 GDI+ 的速度有过不好的体验。我需要一个 HBITMAP 并且 GDI+ 不会直接加载到 HBITMAP 中,因此需要另一个副本。 GDI+ 是一个选项,但不是我最喜欢的。 【参考方案1】:您可以使用Windows Imaging Component 加载PNG 文件(在Windows XP SP2 和更高版本上)。有关如何使用 API 的介绍,请参阅 MSDN Magazine,有关从 IStream 加载 PNG 并将其转换为 HBITMAP 的代码示例,请参阅 my blog post。
【讨论】:
嗨布拉德利,看起来 MSDN 杂志链接已关闭,您能否更新链接并在文章的答案中添加一些示例代码?谢谢! @jrh 是的,微软似乎撤下了该页面,但有一个存档副本可用:web.archive.org/web/20080507014245/http://msdn.microsoft.com/… 请注意,Microsoft converted the older issues to chm format -- 如果您愿意,我可以建议进行编辑,将代码添加到答案中。【参考方案2】:无需使用 Windows Imaging Component、GDI+ 或 PNG 库。您可以使用图标功能。
将新图标 (ICO_PNG) 添加到具有自定义宽度和高度的 VC 项目资源(资源编辑器->图像->新图像类型)。把你的png图片复制到这里,用填充工具+透明色让图标透明。
将图片控制 (IDC_PNG) 添加到您的对话框(类型 = Owner draw)。
对话过程代码:
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;
【讨论】:
相关:How would I draw a PNG image using LoadImage and StretchDIBits?。请注意,此答案似乎仅在 png 是 resource 时才有效,我无法直接从独立文件(例如 myimage.png)加载 png。看来LoadImage
只支持从文件加载.ico 图标。
你确定LoadImage可以加载PNG资源吗?我没有创建IDC,只需要加载资源,但看起来它没有加载。【参考方案3】:
您可以使用StretchDIBits
API 来实现,但受限于操作系统/驱动程序的可用性。
有关详细信息,请参阅 MSDN 文档:
http://msdn.microsoft.com/en-us/library/dd145121(v=VS.85).aspx
http://msdn.microsoft.com/en-us/library/dd145107(VS.85).aspx
对于误导对这个问题感兴趣的各位,我深表歉意。
让我纠正我的错误。
没有 StretchDIBits
用于 PNG 绘图。
您最好尝试 WIC 方法或考虑将 GDI+ 集成到您的项目中的方法。
【讨论】:
+1... 在多次将我的头撞在墙上之后,我很高兴终于看到了这个答案! :) 哎呀!我对此做了一些研究。似乎没有实际将 PNG 图像绘制到 GDI 设备上下文的工作代码。许多人指出 StretchDIBits PNG 支持完全没用。【参考方案4】:我们可以通过 GDI 显示 png 图像,在创建窗口时通过以下两个步骤(窗口过程函数中的 case WM_CREATE
):
-
加载 png 文件(通过 libpng 或 stb 图像),保存在变量中的像素值,例如
buffer
在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”) - 我相应地编辑了您的答案 @MichaelHaephrati 对不起,我不太同意你的意见。我把libpng.lib的具体路径粘贴为“F:\zhangzhuo\lib\opencv_249\build\x64\vc12\staticlib\libpng.lib”之类的东西,是为了告诉大家可以使用自带的opencv 249 prebuild包图书馆。人们很容易获得该版本的预建 opencv,因此他们不必手动编译 zlib 和 libpng,这很耗时。人们当然应该修改该路径,因为人们可能有不同的路径。 好的。我明白了【参考方案5】:vladimir_hr 的答案就是简单。
简单的步骤。
在资源头文件中声明如下: #define IDI_PNG 1000
在资源文件 *.rc 中有: IDI_PNG ICON "量角器.ico"
图标文件。 使用支持自定义大小而不是标准 Windows 图标大小的图标编辑器将您的(透明)png 文件转换为图标文件,将此 png 图像保存为图标图像。
其余的只是在 DC 之间传输。
【讨论】:
如果您指的是另一个答案,请对该答案发表评论。以上是关于如何使用 Win32/GDI 加载 PNG 图像(如果可能,不要使用 GDI+)?的主要内容,如果未能解决你的问题,请参考以下文章
来自 wchar_t* 的 C++ Win32 GDI+ 拉绳
SDL/Pygame 无法使用 cx_Freeze 加载 PNG 图像
如何检查加载到 FMX.TBitmap 的 PNG 图像是不是具有 alpha 通道?