基於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圖片 (代碼無法運行)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM