使用 Libpng 配合 GDI 完成對 png 圖片的解析與顯示


 

使用 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通道, 用於實現部分透明效果。圖片如下:

圖: 背景圖片 bk.png 圖: 前景圖片 op.png

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 型的位圖, 將位圖句柄 hPngFrhPngBk 指向這兩張新創建的位圖。

接下來就是在處理 WM_PAINT 消息時進行 png 的繪圖操作, 創建前景、背景的設備環境句柄 hdcFrhdcBk, 將位圖句柄分別選入到設備環境中。

顯示是通過兩個函數來完成的, BitBltAlphaBlend。 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

 

 

 


免責聲明!

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



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