使用 Libpng 配合 GDI 完成對 png 圖片的解析與顯示
第一步: 使用 libpng 完成對 png 圖像的解析
在上一篇 《VC6 下 libpng 庫的編譯與初步使用》 中我們已經完成了對 libpng 庫的編譯與配置, 今天就來用它來實現對 png 圖片進行解析並且將解析到的圖片數據通過 Windows GDI 顯示到窗口中。
在這之前, 我們先做個假設:
- 1. 所使用的圖片確實為 png 格式
做假設目的是為了減少演示代碼的復雜度, 因為代碼中沒有對傳入的圖片是否為png進行檢測, 進行過多的錯誤處理這不符合演示代碼的書寫習慣。
開始進行圖片得讀取:
1 long ReadPngData( const char *szPath, int *pnWidth, int *pnHeight, unsigned char **cbData ) 2 { 3 FILE *fp = NULL; 4 long file_size = 0, pos = 0, mPos = 0; 5 int color_type = 0, x = 0, y = 0, block_size = 0; 6 7 png_infop info_ptr; 8 png_structp png_ptr; 9 png_bytep *row_point = NULL; 10 11 fp = fopen( szPath, "rb" ); 12 if( !fp ) return FILE_ERROR; //文件打開錯誤則返回 FILE_ERROR 13 14 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); //創建png讀取結構 15 info_ptr = png_create_info_struct(png_ptr); //png 文件信息結構 16 png_init_io(png_ptr, fp); //初始化文件 I\O 17 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); //讀取png文件 18 19 *pnWidth = png_get_image_width( png_ptr, info_ptr ); //獲得圖片寬度 20 *pnHeight = png_get_image_height( png_ptr, info_ptr ); //獲得圖片高度 21 color_type = png_get_color_type( png_ptr, info_ptr ); //獲得圖片顏色類型 22 file_size = (*pnWidth) * (*pnHeight) * 4; //計算需要存儲RGB(A)數據所需的內存大小 23 *cbData = (unsigned char *)malloc(file_size); //申請所需的內存, 並將傳入的 cbData 指針指向申請的這塊內容 24 25 row_point = png_get_rows( png_ptr, info_ptr ); //讀取RGB(A)數據 26 27 block_size = color_type == 6 ? 4 : 3; //根據是否具有a通道判斷每次所要讀取的數據大小, 具有Alpha通道的每次讀4字節, 否則讀3字節 28 29 //將讀取到的RGB(A)數據按規定格式讀到申請的內存中 30 for( x = 0; x < *pnHeight; x++ ) 31 for( y = 0; y < *pnWidth*block_size; y+=block_size ) 32 { 33 (*cbData)[pos++] = row_point[x][y + 2]; //B 34 (*cbData)[pos++] = row_point[x][y + 1]; //G 35 (*cbData)[pos++] = row_point[x][y + 0]; //R 36 (*cbData)[pos++] = row_point[x][y + 3]; //Alpha 37 } 38 39 png_destroy_read_struct(&png_ptr, &info_ptr, 0); 40 fclose( fp ); 41 42 return file_size; 43 }
上面我們定義了一個 ReadPngData 的函數, 看一下這個函數的參數:
long ReadPngData( const char *szFileName, //png文件路徑 int *pnWidth, //指針類型, 用於傳出圖片寬度 int *pnHeight, //指針類型, 用於傳出圖片高度 unsigned char **cbData //二級指針, 用於指向所申請的用於存儲RGB(A)的內存地址 )
在代碼中, 還根據獲得的色彩類型對每次需要讀取的字節數做了簡單的判斷
block_size = color_type == 6 ? 4 : 3; //根據是否具有a通道判斷每次所要讀取的數據大小, 具有Alpha通道的每次讀4字節, 否則讀3字節
該語句使用了問號表達式代替了 if..else 進行判斷, 減少了代碼的行數。
接下來就是使用兩個 for 循環完成對RGB(A)數據的讀取工作, 在這里, 你也可以通過不同的 RGB(A) 之間的運算, 將圖像呈現出不同的顯示效果。
現在, cbData指針指向的內存中就是我們所需的 RGB(A) 數據了, 有了這份數據, 就可以完成顯示。該函數已被封裝為 .h 頭文件, 使用可以直接調用, 完成的封裝代碼如下: gdipng.h
View Code - gdipng.h
#pragma once ////////////////////////////////////////////////////////////////////////// #include <stdio.h> #include "png.h" ////////////////////////////////////////////////////////////////////////// #define FILE_ERROR -1 ////////////////////////////////////////////////////////////////////////// long ReadPngData( const char *szFileName, int *pnWidth, int *pnHeight, unsigned char **cbData ); ////////////////////////////////////////////////////////////////////////// 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 FILE_ERROR; //文件打開錯誤則返回 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; } //////////////////////////////////////////////////////////////////////////
第二步: 通過 GDI 實現 png 的顯示
這里准備了兩張png圖片, 一個作為背景(bk.png), 一張作為前景(op.png), 圖片來源於互聯網, 前景圖具有 Alpha通道, 用於實現部分透明效果。圖片如下:
1. 創建一個Win32窗口(已折疊)
View Code - WinMain
#include <windows.h> #include "gdipng.h" ////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ); ////////////////////////////////////////////////////////////////////////// int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdline, int iCmdShow ) { TCHAR szAppName[] = TEXT("GdiPng"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hbrBackground = GetSysColorBrush( BLACK_BRUSH ); wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WndProc; wndclass.lpszClassName = szAppName; wndclass.lpszMenuName = NULL; wndclass.style = CS_HREDRAW | CS_VREDRAW; if( !RegisterClass(&wndclass) ) { MessageBox( NULL, TEXT("窗口類注冊失敗!"), TEXT("應用程序錯誤"), MB_OK | MB_ICONERROR ); return 0; } hwnd = CreateWindow( szAppName, TEXT("通過 GDI 實現 png 的顯示 - Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL ); ShowWindow( hwnd, iCmdShow ); UpdateWindow( hwnd ); while( GetMessage(&msg, NULL, 0, 0) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } return msg.wParam; }
2. 在處理 WM_PAINT 消息時進行顯示
整個窗口回調函數部分的代碼如下:
1 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 2 { 3 static HBITMAP hPngFr, hPngBk; //前景、背景位圖句柄 4 HDC hdc, hdcFr, hdcBk; //客戶區、前景、背景設備句柄 5 PAINTSTRUCT ps ; 6 static fr_x, fr_y; //前景圖寬、高 7 static bk_x, bk_y; //背景圖寬、高 8 9 static unsigned char *cbFrData=NULL; //用於指向前景圖的RGB(A)數據內存地址 10 static unsigned char *cbBkData=NULL; //用於指向背景圖的RGB(A)數據內存地址 11 12 BLENDFUNCTION bf = {0}; //BLENDFUNCTION結構變量, 作為函數參數, 用於 AlphaBlend 函數進行 Alpha 的融圖 13 bf.BlendOp = AC_SRC_OVER; 14 bf.BlendFlags = 0; 15 bf.AlphaFormat = AC_SRC_ALPHA; 16 bf.SourceConstantAlpha = 255; //透明度, 范圍為 0-255, 0:透明, 255:不透明 17 18 switch (message) 19 { 20 case WM_CREATE: //處理窗口創建消息 21 ReadPngData( "op.png", &fr_x, &fr_y, &cbFrData ); //讀取前景圖 op.png 數據 22 ReadPngData( "bk.png", &bk_x, &bk_y, &cbBkData ); //讀取背景如 bk.png 數據 23 hPngFr = CreateBitmap( fr_x, fr_y, 32, 1, cbFrData ); //創建前景位圖 24 hPngBk = CreateBitmap( bk_x, bk_y, 32, 1, cbBkData ); //創建背景位圖 25 return 0 ; 26 27 case WM_PAINT: //處理重繪消息 28 hdc = BeginPaint (hwnd, &ps) ; //開始重繪 29 30 hdcFr = CreateCompatibleDC( hdc ); //創建前景設備句柄 31 SelectObject( hdcFr, hPngFr ); //將前景圖句柄選入設備環境 32 33 hdcBk = CreateCompatibleDC( hdc ); //創建背景設備句柄 34 SelectObject( hdcBk, hPngBk ); //將背景圖句柄選入設備環境 35 36 BitBlt( hdc, 0, 0, bk_x, bk_y, hdcBk, 0, 0, SRCCOPY ); //將背景圖進行 BitBlt 操作顯示在客戶區中 37 AlphaBlend( hdc, 100, 0, fr_x, fr_y, hdcFr, 0, 0, fr_x, fr_y, bf ); //使用 AlphaBlend 函數將前景與背景進行融合 38 39 DeleteDC( hdcFr ); //刪除前景圖設備環境句柄 40 DeleteDC( hdcBk ); //刪除背景圖設備環境句柄 41 EndPaint( hwnd, &ps ); //結束重繪 42 return 0 ; 43 44 case WM_DESTROY: //處理撤銷窗口消息 45 PostQuitMessage (0) ; 46 return 0 ; 47 } 48 49 return DefWindowProc (hwnd, message, wParam, lParam) ; 50 }
Wnd_Main.c 完整代碼(已折疊):
View Code - W_Main.c
#include <windows.h> #include "gdipng.h" ////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ); ////////////////////////////////////////////////////////////////////////// int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdline, int iCmdShow ) { TCHAR szAppName[] = TEXT("GdiPng"); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hbrBackground = GetSysColorBrush( BLACK_BRUSH ); wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wndclass.hInstance = hInstance; wndclass.lpfnWndProc = WndProc; wndclass.lpszClassName = szAppName; wndclass.lpszMenuName = NULL; wndclass.style = CS_HREDRAW | CS_VREDRAW; if( !RegisterClass(&wndclass) ) { MessageBox( NULL, TEXT("窗口類注冊失敗!"), TEXT("應用程序錯誤"), MB_OK | MB_ICONERROR ); return 0; } hwnd = CreateWindow( szAppName, TEXT("通過 GDI 實現 png 的顯示 - Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL ); ShowWindow( hwnd, iCmdShow ); UpdateWindow( hwnd ); while( GetMessage(&msg, NULL, 0, 0) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } return msg.wParam; } ////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hPngFr, hPngBk; //前景、背景位圖句柄 HDC hdc, hdcFr, hdcBk; //客戶區、前景、背景設備句柄 PAINTSTRUCT ps ; static fr_x, fr_y; //前景圖寬、高 static bk_x, bk_y; //背景圖寬、高 static unsigned char *cbFrData=NULL; //用於指向前景圖的RGB(A)數據內存地址 static unsigned char *cbBkData=NULL; //用於指向背景圖的RGB(A)數據內存地址 BLENDFUNCTION bf = {0}; //BLENDFUNCTION結構變量, 作為函數參數, 用於 AlphaBlend 函數進行 Alpha 的融圖 bf.BlendOp = AC_SRC_OVER; bf.BlendFlags = 0; bf.AlphaFormat = AC_SRC_ALPHA; bf.SourceConstantAlpha = 255; //透明度, 范圍為 0-255, 0:透明, 255:不透明 switch (message) { case WM_CREATE: //處理窗口創建消息 ReadPngData( "op.png", &fr_x, &fr_y, &cbFrData ); //讀取前景圖 op.png 數據 ReadPngData( "bk.png", &bk_x, &bk_y, &cbBkData ); //讀取背景如 bk.png 數據 hPngFr = CreateBitmap( fr_x, fr_y, 32, 1, cbFrData ); //創建前景位圖 hPngBk = CreateBitmap( bk_x, bk_y, 32, 1, cbBkData ); //創建背景位圖 return 0 ; case WM_PAINT: //處理重繪消息 hdc = BeginPaint (hwnd, &ps) ; //開始重繪 hdcFr = CreateCompatibleDC( hdc ); //創建前景設備句柄 SelectObject( hdcFr, hPngFr ); //將前景圖句柄選入設備環境 hdcBk = CreateCompatibleDC( hdc ); //創建背景設備句柄 SelectObject( hdcBk, hPngBk ); //將背景圖句柄選入設備環境 BitBlt( hdc, 0, 0, bk_x, bk_y, hdcBk, 0, 0, SRCCOPY ); //將背景圖進行 BitBlt 操作顯示在客戶區中 AlphaBlend( hdc, 100, 0, fr_x, fr_y, hdcFr, 0, 0, fr_x, fr_y, bf ); //使用 AlphaBlend 函數將前景與背景進行融合 //TransparentBlt( hdc, 200, 0, fr_x, fr_y, hdcFr, 0, 0, fr_x, fr_y, RGB(255, 255, 255) ); //對純色背景的透明操作 DeleteDC( hdcFr ); //刪除前景圖設備環境句柄 DeleteDC( hdcBk ); //刪除背景圖設備環境句柄 EndPaint( hwnd, &ps ); //結束重繪 return 0 ; case WM_DESTROY: //處理撤銷窗口消息 PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
運行效果如下:
關於 AlphaBlend 函數
該函數用來顯示具有Alpha通道的圖像, 將兩個圖像按照圖片得Alpha數據以及指定的透明度進行融合。其函數原型:
BOOL AlphaBlend( HDC hdcDest, // 目標環境設備句柄 int nXOriginDest, // 目標環境設備的x坐標 int nYOriginDest, // 目標環境設備的y坐標 int nWidthDest, // 目標矩形區域的寬度 int nHeightDest, // 目標矩形區域的高度 HDC hdcSrc, // 源設備環境句柄 int nXOriginSrc, // 源環境設備的x坐標 int nYOriginSrc, // 源環境設備的y坐標 int nWidthSrc, // 源矩形區域的寬度 int nHeightSrc, // 源矩形區域的高度 BLENDFUNCTION blendFunction // BLENDFUNCTION結構, 指定融合的方式 );
注意: 該函數在使用時需要在IDE的對象\模塊庫中添加 msimg32.lib, 或者使用 #pragma comment(lib, "msimg32.lib") 命令。
關於 BLENDFUNCTION 結構
BLENDFUNCTION 結構在 MSDN中的定義:
typedef struct _BLENDFUNCTION { BYTE BlendOp; // 指定為 AC_SRC_OVER BYTE BlendFlags; // 必須為 0 BYTE SourceConstantAlpha; // 源位圖的透明度, 范圍為 0-255, 0:透明, 255:不透明 BYTE AlphaFormat; // 指定為 AC_SRC_ALPHA }BLENDFUNCTION;
代碼解說:
在這段代碼中, 首先在處理 WM_CREATE 消息時對前景png圖以及背景png圖進行讀取, 同時, 也獲取到了這兩張png圖片得寬度和高度, 再通過 CreateBitmap 函數將得到的 RGB(A) 數據轉為 BITMAP 型的位圖, 將位圖句柄 hPngFr 和 hPngBk 指向這兩張新創建的位圖。
接下來就是在處理 WM_PAINT 消息時進行 png 的繪圖操作, 創建前景、背景的設備環境句柄 hdcFr 和 hdcBk, 將位圖句柄分別選入到設備環境中。
顯示是通過兩個函數來完成的, BitBlt 和 AlphaBlend。 BltBlt 將 RGB 數據進行輸出, 不自動處理 Alpha 數據。 AlphaBlend 函數負責前后背景的融圖。
最后就是刪除創建的設備環境句柄 DeleteDC( hdcFr ); 、 DeleteDC( hdcBk );。
三、處理純色背景的png圖片
有些 png 不具備a通道, 但是背景為純色, 如下圖中的一座浮島的png圖片(il.png)所示:

通過觀察可以發現, 該圖片得背景為 RGB(255, 255, 255) 的純白色背景, 對於這樣的圖片, 要實現純色部分的透明顯示最簡單的辦法就是借助 TransparentBlt 函數, 該函數的作用是將圖片中的某一種顏色當做透明顏色並與目標設備中的顏色進行融合, 其函數的原型:
BOOL TransparentBlt( HDC hdcDest, // 目標設備環境的句柄 int nXOriginDest, // 目標矩形左上角的x軸坐標 int nYOriginDest, // 目標矩形左上角的y軸坐標 int nWidthDest, // 目標矩形寬度 int hHeightDest, // 目標矩形高度 HDC hdcSrc, // 源設備環境句柄 int nXOriginSrc, // 源矩形左上角的x軸坐標 int nYOriginSrc, // 源標矩形左上角的y軸坐標 int nWidthSrc, // 源矩形寬度 int nHeightSrc, // 源矩形高度 UINT crTransparent // 源位圖中的RGB值當作透明顏色 );
現在將 BitBlt 和 AlphaBlend 操作替換為 BitBlt 和 TransparentBlt 操作:
BitBlt( hdc, 0, 0, bk_x, bk_y, hdcBk, 0, 0, SRCCOPY ); TransparentBlt( hdc, 200, 0, fr_x, fr_y, hdcFr, 0, 0, fr_x, fr_y, RGB(255, 255, 255) );
運行效果如圖:
這種處理方式有個弊端就是, 當圖像內部也用到與將被扣去的顏色值時, 摳出來的效果就變得不符合我們期望值, 這時, 通過 PS 或者其他圖像處理軟件對圖片進行二次處理是個不錯的辦法。
本篇博文中的代碼與示例素材下載(含VC6工程文件): http://files.cnblogs.com/mr-wid/gdipng_demo.zip
--------------------
wid, 2013.04.22
