工作中,做一些炫點的界面都需要用到PNG圖片,Wince里面微軟也提供了PNG圖片的支持,不過Alpha的混合速度比較慢,所以自己實現了一個Alpha的混合運算接口,經過測試,要比微軟AlphaBlend快4、5倍。當然Alpha混合的方法也適合window下的VC使用。下面有測試的數據。
原創博文,需要轉載,請標明出處:http://www.cnblogs.com/mythou/p/3150396.html
1、創建兼容32位位圖。
一般界面貼圖,我們都是使用微軟的兼容DC和兼容位圖進行處理。不過這里我需要創建一張32位的設備無關位圖。(有關DIB位圖相關知識,不了解的可以百度一下,這是和兼容位圖對應的一種位圖格式。這里就不多說了)。
/*
*hDC:兼容的DC指針
*nWidth:需要創建DIB位圖寬
*nHeight:創建位圖高
*pBitmapData:這是我自己定義的數據類型,就是一個unsigned char * 類型
*/
HBITMAP CPngBitBlt::Create32Bitmap(HDC hDC, int nWidth, int nHeight,HBMDC &pBitmapData) { if(hDC == NULL) { return NULL; } if(nWidth==0 || nHeight==0) { return NULL; } BITMAPINFO *pbinfo = new BITMAPINFO; if(!pbinfo) { return NULL; } ZeroMemory(pbinfo, sizeof(BITMAPINFO)); pbinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbinfo->bmiHeader.biWidth = nWidth; pbinfo->bmiHeader.biHeight = -nHeight;//位圖翻轉 pbinfo->bmiHeader.biPlanes = 1; pbinfo->bmiHeader.biBitCount = 32;//32位位圖 BYTE* lpBitmapBits = NULL; HBITMAP hDIBmp = CreateDIBSection(hDC, pbinfo, DIB_RGB_COLORS, (void **)&lpBitmapBits, NULL, 0); pBitmapData = lpBitmapBits; delete pbinfo; if(hDIBmp == NULL) { return NULL; } return hDIBmp; }
上面的函數是創建一張指定大小的DIB位圖,而且返回這張位圖的數據指針(指向位圖數據的首指針)。
這張位圖,一般就是用來當做背景位圖,我們一般貼圖使用的雙緩沖方式,需要前后兩張位圖,這個DIB位圖就是用做內存DC的位圖。
2、加載PNG圖片
PNG圖片加載,這里還是使用微軟的IImage解碼PNG圖片,使用過pnglib解碼庫,實際解碼速度還比不上IImage,也可能是我編譯的libpng庫沒有做優化吧。所以暫時放棄使用libpng。后續有時間會研究一下libpng解碼的原理和嵌入式上優化方式。Android上也是使用libpng解碼png,所以解碼速度應該還是可以的,不過估計需要做一些嵌入式平台的化,比較大部分嵌入式平台機器CPU主頻都不高,(現在的手機例外 -_-!),我工作接觸的一般也就400M到800M的范圍。下面是解碼部分代碼,主要是從IImage解碼PNG圖片,然后把PNG圖片構造一個DIB位圖,獲取一個可以操作圖片的指針。
BOOL CImageData::LoadImageFromFile(const TCHAR *fileName) { // 參數有效性
if (fileName == NULL) { return FALSE; } // 創建COM實例
HRESULT hr = NULL; if(FAILED(hr = ::CoCreateInstance(CLSID_ImagingFactory,NULL,CLSCTX_INPROC_SERVER,IID_IImagingFactory,(void**) &m_pImagingFactory))) { return FALSE; }// 從文件中創建圖片
if(FAILED(hr = m_pImagingFactory->CreateImageFromFile(fileName, &m_pImage))) { return FALSE; } // 得到圖片信息
if(FAILED(hr = m_pImage->GetImageInfo(&m_ImageInfo))) { return FALSE; } // 獲取原始數據
if (ReadImageData() == FALSE) { return FALSE; } //......// 成功獲得圖片信息
return TRUE; }
BOOL CImageData::ReadImageData() { // 用於返回結果
BOOL bRet = TRUE; // 參數有效性
if (m_pImage==NULL || m_pImagingFactory==NULL) { return FALSE; } // 取得圖片原始數據
RECT rect = {0, 0, m_ImageInfo.Width, m_ImageInfo.Height}; BitmapData bitmapData; bitmapData.Width = m_ImageInfo.Width; bitmapData.Height = m_ImageInfo.Height; bitmapData.PixelFormat = m_ImageInfo.PixelFormat; IBitmapImage *pBitmapImage = NULL; m_pImagingFactory->CreateBitmapFromImage(m_pImage, m_ImageInfo.Width, m_ImageInfo.Height, PIXFMT_32BPP_ARGB, InterpolationHintDefault, &pBitmapImage); pBitmapImage->LockBits(&rect, ImageLockModeRead, PIXFMT_32BPP_ARGB, &bitmapData); // 釋放舊數據
if (m_pImgDataBuf != NULL) { delete[] m_pImgDataBuf; m_pImgDataBuf = NULL; } // 申請新空間
bRet = TRUE; m_pImgDataBuf = new unsigned char[m_ImageInfo.Width * m_ImageInfo.Height * 4]; if (m_pImgDataBuf == NULL) { bRet = FALSE; goto ERROR_END_FUNCTION; } // 拷貝數據
bRet = TRUE; memcpy(m_pImgDataBuf, bitmapData.Scan0, m_ImageInfo.Width * m_ImageInfo.Height * 4); ERROR_END_FUNCTION: pBitmapImage->UnlockBits(&bitmapData); pBitmapImage->Release(); return bRet; }
上面主要m_pImgDataBuf 就是一個Byte *的指針,一個指向PNG圖片數據的起始指針。我們利用這個指針就可以操作我們剛剛解碼的PNG圖片的數據。
3、Alpha混合
首先說說Alpha混合,Alpha是指PNG圖片的透明度,一般我們使用PNG圖片就是為了可以有半透明的顯示效果,大部分比較炫的界面,都有這些半透明特效使用,Alpha就是指定圖片的透明度,一般圖片的透明度我們可以分為兩種。
第一:整張圖片的Alpha值,圖片每個像素使用同一個Alpha值。
第二:每個像素使用獨立的Alpha值。
對於32位的PNG圖片來說,每個像素值,都保存了自己的Alpha值,32位PNG的像素格式:ARGB8888就是,每個值都是一個8位值。
Alpha混合的基本公式如下:AlphaResult = ALPHA * srcPixel + ( 1 - ALPHA ) * destPixel
下面給出一個Alpha的混合算法例子。
//支持自定義Alpha
//pBackDSSrcBmp:我們背景DIB位圖的數據指針,也就是上面創建的DIB位圖返回的指針pBitmapData
//m_ImgDataBuf:上面解碼圖片,獲取的需要顯示的PNG圖片的數據指針
//支持自定義Alpha void CImageData::DrawImage(HBMDC pBackDCSrcBmp, const int DstX, const int DstY, const int Alpha, DWORD DstBMPWidth) { //進行Alpha混合運算 BYTE btAlphaSRC = 0; DWORD iSrcPos = 0; int iDstPos = 0; for(DWORD i=0; i<m_ImageHeigh; i++) { DWORD SrcData = (i+DstY)*DstBMPWidth + DstX; DWORD DstData = i*m_ImageWidth; for(int j=0; j<m_ImageWidth; j++) { // 計算源圖像數據索引和像素點ALPHA iSrcPos = (SrcData + j) << 2; iDstPos = (DstData + j) << 2; btAlphaSRC = m_pImgDataBuf[iDstPos+3]; btAlphaSRC = btAlphaSRC*Alpha >> 8; //ALPHA混合基本公式result = ALPHA * srcPixel + ( 1 - ALPHA ) * destPixel pBackDCSrcBmp[iSrcPos] += (btAlphaSRC*(m_pImgDataBuf[iDstPos]-pBackDCSrcBmp[iSrcPos]) >> 8); pBackDCSrcBmp[iSrcPos+1] += (btAlphaSRC*(m_pImgDataBuf[iDstPos+1]-pBackDCSrcBmp[iSrcPos+1]) >> 8); pBackDCSrcBmp[iSrcPos+2] += (btAlphaSRC*(m_pImgDataBuf[iDstPos+2]-pBackDCSrcBmp[iSrcPos+2]) >> 8); } } }
背景圖和需要顯示的PNG圖片,兩張圖片對應像素值,根據Alpha混合混合,就可以得到最終的圖片,這個就是整個顯示過程。
這里的方法不僅僅針對PNG圖片,其實對於jpg和bmp也是有效的,只是這兩種圖片,一般是不包含alpha信息的,所以只能控制整張圖片的透明度,而不能控制每個像素的透明度。
上面的運算公式經過一些處理,主要是減少運算和減少乘除法的使用,比較cpu運行乘除法比不上加減法。
下面是我自己測試的數據結果:
這個是我在一台主頻是600M的CPU上面運行的速度對比,速度單位是毫秒,可以對比,使用自己的Alpha混合要比微軟的AlpBlend快不少,特別是針對大圖片來說。
查過一些資料,大部分人的觀點是微軟的AlphaBlend里面處理過多的異常處理和對圖片伸縮進行操作,導致比較慢。
有需要朋友可以自己根據我上面說的封裝一個png貼圖類,我前面寫了一篇使用微軟ALphBlend貼圖的文章,里面給出了完整代碼。
這個自定義的方法,代碼比較多,我就不貼出來的。
微軟AlphaBlend貼圖類:http://www.cnblogs.com/mythou/archive/2013/06/13/3133606.html
如果有需要的朋友可以根據自己情況封裝不同的接口處理,如果有不了解地方,可以留言。
新建的討論群,有興趣可以加入
VC/Wince群:87053214