概述
這篇的標題更確切的說應該叫位圖畫刷,這樣才好和前幾篇對應起來。在Direct2D中,位圖的渲染也是通過畫刷來實現的。
Direct2D中並沒有直接操作位圖的接口,而是借助WIC(Windows Image Component)來完成的。今天我們來看看如何在Direct2D中加載並顯示位圖。這個方法可以用來渲染背景。基本步驟如下。
- 從文件創建WIC位圖
- 由WIC位圖創建D2D位圖
- 使用D2D繪制位圖
在開始之前,首先簡要介紹一下WIC
什么是WIC?
WIC全稱是Windows Image Component,是一套擴展的API,用來處理數字圖像,它是基於COM組件的。該API包含非常豐富的圖像處理函數,比如
- 內置對於標准web image格式的解碼支持
- 內置對於標准metadata格式的支持
- 廣泛的像素格式支持
- 高色度支持,包含30位擴展,30位及48位高精度像素格式
- 對於圖像解碼,像素格式及元數據格式的擴展框架支持
WIC包含的組件及每個組件中的接口如下圖所示。
在這里,我們只要知道WIC能夠處理圖像即可,比如位圖操作。關於WIC的詳細信息,大家可以看看MSDN的介紹。
具體步驟
從文件創建WIC位圖
給定一個圖像文件,我們首先使用WIC函數將其讀入內存,並創建一個WIC類型的位圖。
首先我們需要創建一個解碼器,因為圖片是經過編碼的,為了能顯示圖片,我們首先需要將其解碼,創建解碼器需要使用函數CreateDecoderFromFilename,該函數返回一個解碼器指針。稍后的操作都通過這個指針來完成,關於這個函數的詳細介紹,可以參考MSDN,這里不再贅述。
然后,利用創建好的解碼器來獲取圖片的幀,我么這里只要第一幀,因為圖片只有一幀,但是對於視頻文件來說,就有許多幀了。代碼如下:在這里,uri即圖片文件名。
HRESULT LoadBitmapFromFile( ID2D1RenderTarget *pRenderTarget, IWICImagingFactory *pIWICFactory, PCWSTR uri, UINT destinationWidth, UINT destinationHeight, ID2D1Bitmap **ppBitmap ) { HRESULT hr = S_OK; IWICBitmapDecoder *pDecoder = NULL; IWICBitmapFrameDecode *pSource = NULL; IWICStream *pStream = NULL; IWICFormatConverter *pConverter = NULL; IWICBitmapScaler *pScaler = NULL; hr = pIWICFactory->CreateDecoderFromFilename( uri, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder ); if (SUCCEEDED(hr)) { // Create the initial frame. hr = pDecoder->GetFrame(0, &pSource); }
然后創建converter,負責對位圖進行后續的格式轉換。
if (SUCCEEDED(hr)) { hr = pIWICFactory->CreateFormatConverter(&pConverter); }
接下來則要判斷圖像是否被放大或者縮小了,比如一個圖片的原始尺寸是100 x 100,但是我們程序中要以 200 x 200的方式去顯示,那么相當於將圖片放大了一倍,圖片的顯示尺寸通過參數來指定,而實際尺寸則是通過分析圖片文件得到。如果圖片有縮放,那么需要從新生成圖片的數據文件,如果沒有,那么直接進行下一步即可。代碼如下:
// If a new width or height was specified, create an // IWICBitmapScaler and use it to resize the image. if (destinationWidth != 0 || destinationHeight != 0) { UINT originalWidth, originalHeight; hr = pSource->GetSize(&originalWidth, &originalHeight); if (SUCCEEDED(hr)) { if (destinationWidth == 0) { FLOAT scalar = static_cast<FLOAT>(destinationHeight) / static_cast<FLOAT>(originalHeight); destinationWidth = static_cast<UINT>(scalar * static_cast<FLOAT>(originalWidth)); } else if (destinationHeight == 0) { FLOAT scalar = static_cast<FLOAT>(destinationWidth) / static_cast<FLOAT>(originalWidth); destinationHeight = static_cast<UINT>(scalar * static_cast<FLOAT>(originalHeight)); } hr = pIWICFactory->CreateBitmapScaler(&pScaler); if (SUCCEEDED(hr)) { hr = pScaler->Initialize( pSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic ); } if (SUCCEEDED(hr)) { hr = pConverter->Initialize( pScaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut ); } } }
由WIC位圖創建D2D位圖
調用函數CreateBitmapFromWicBitmap可以由一個WIC位圖創建一個D2D位圖,代碼如下:
if (SUCCEEDED(hr)) { // Create a Direct2D bitmap from the WIC bitmap. hr = pRenderTarget->CreateBitmapFromWicBitmap( pConverter, NULL, ppBitmap ); }
上面這些代碼有個特點,就是要時刻判斷前一次函數調用的返回值,只有前面的操作成功了,才進行下一步操作。這是很好的編程習慣。
最后,需要做一些清理工作,由於WIC是基於COM的,所以,需要手動釋放COM對象,代碼如下:
SAFE_RELEASE(pDecoder);
SAFE_RELEASE(pSource);
SAFE_RELEASE(pStream);
SAFE_RELEASE(pConverter);
SAFE_RELEASE(pScaler);
SAFE_RELEASE是一個宏定義
#define SAFE_RELEASE(P) if(P){P->Release() ; P = NULL ;}
使用D2D繪制位圖
這一步就很簡單了,繪制位圖和繪制其他幾何圖形幾乎沒有區別。首先是將render target清空為指定顏色,也就是背景色,然后調用render target的接口DrawBitmap來繪制位圖,這個函數需要指定位圖的尺寸,所以之前還需要獲取位圖的大小。注意繪制代碼要放在BeginDraw和EndDraw之間。
void DrawBitmap() { CreateD2DResource(g_Hwnd) ; pRenderTarget->BeginDraw() ; // Clear background color to dark cyan pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White)); D2D1_SIZE_F size = pBitmap->GetSize() ; D2D1_POINT_2F upperLeftCorner = D2D1::Point2F(0.f, 0.f) ; // Draw bitmap pRenderTarget->DrawBitmap( pBitmap, D2D1::RectF( upperLeftCorner.x, upperLeftCorner.y, upperLeftCorner.x + size.width, upperLeftCorner.y + size.height) ) ; HRESULT hr = pRenderTarget->EndDraw() ; if (FAILED(hr)) { MessageBox(NULL, "Draw failed!", "Error", 0) ; return ; } }
最后,來一張效果圖,這是微軟的游戲 4 Elements 2 的截圖,大家一同欣賞一下。這是我平生購買的第一款游戲,值得紀念一下。
==
Happy Coding!!! 永遠不要放棄自己的夢想。