基于GDI显示png图像
intro
先前基于GDI已经能够显示BITMAP图像:windows下控制台程序实现窗口显示 ,其中BMP图像是使用LoadImage()
这一Win32 API函数来做的。考虑到LoadImage()
函数并不能读取png图像,因此需要libpng或stb等png编解码库的帮助。
网上找到相关代码不多,稍加修改可以运行,具备特点:
-
纯C,单个文件(依赖的libpng和zlib可以忽略)
-
直接读取png图像而不是通过读取.rc文件(资源文件)再读取png图像
-
png图像的读取:基于libpng(以及zlib),我直接用的opencv345 windows预编译报里的.h文件和库文件
-
入口函数为
main()
而非WinMain()
,也即控制台程序,方便作为库函数、移植 -
不同于BMP的地方:在窗口处理函数的创建阶段有所不同:
case WM_CREATE: if (image_type == BMP) { my_window->hBmp = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); } else if (image_type == PNG) { ReadPngData("D:/work/libfc/imgs/Lena.png", &im_width, &im_height, &my_window->imdata); my_window->hBmp = CreateBitmap(im_width, im_height, 32, 1, my_window->imdata); } -
并没有处理alpha通道
如果图像有alpha通道,透明区域将显示为黑色,因为用的是BitBlt()
。如果要使用alpha通道(例如做融合,或者显示为黑色以外的颜色),则应当使用AlphaBlend()
和msing32库:#pragma comment(lib, "msimg32.lib") ... BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, SRCCOPY); //AlphaBlend(hdc, 100, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, bm.bmWidth, bm.bmHeight, bf); opencv中看起来也是把透明区域显示为黑色。
-
缺点:窗口client区域大小固定,没有能根据图像尺寸来变化
代码
#include <stdio.h> #include <windows.h> #include "png.h" #define CRTDBG_MAP_ALLOC #include <crtdbg.h> #pragma comment(lib, "D:/work/libfc/lib/libpng.lib") #pragma comment(lib, "D:/work/libfc/lib/zlib.lib") typedef struct MyRect { int x, y, width, height; } MyRect; const char* project_root = "D:/work/libfc"; 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; //文件打开错误则返回 FILE_ERROR png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); //创建png读取结构 info_ptr = png_create_info_struct(png_ptr); //png 文件信息结构 png_init_io(png_ptr, fp); //初始化文件 I\O png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); //读取png文件 *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; //计算需要存储RGB(A)数据所需的内存大小 *cbData = (unsigned char *)malloc(file_size); //申请所需的内容, 并将 *cbData 指向申请的这块内容 row_point = png_get_rows(png_ptr, info_ptr); //读取RGB(A)数据 block_size = color_type == 6 ? 4 : 3; //根据是否具有a通道判断每次所要读取的数据大小, 具有Alpha通道的每次读4位, 否则读3位 //将读取到的RGB(A)数据按规定格式读到申请的内存中 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, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); } else if (image_type == PNG) { ReadPngData("D:/work/libfc/imgs/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; }
参考
使用 Libpng 配合 GDI 完成对 png 图片的解析与显示 , 讲解细致
How would I load a PNG image using Win32/GDI (no GDI+ if possible)? (没用)
使用libpng和GDI读取显示png图片 (代码无法运行)
Greatness is never a given, it must be earned.
标签:
libfc
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
2013-05-11 vim的配置:自己修改.vimrc文件,写自己风格的配置