(半) 透明位元影像原理及代碼
摘自<<http://www.bc99.cn/Article/ASPX/2007-3-27-cs/65D5KI8EJFGK.html>>
繪製半透明位元影像
有的時侯,我們希望顯示一幅半透明的位元影像。也就是說我們將一幅位元影像B
顯示到A位元影像上,又希望透過B位元影像看到A位元影像的一部分映像但不是全部。比如A位
圖是一幅曲線圖,B是一幅提示位元影像,我們想在顯示提示的同時看到已顯示的曲
線,但不需要曲線的背景,就需有用到半透明位元影像。曲線看上去就象從B位元影像中滲
透過來,其實半透明技術就是一種滲透技術,滲透公式我們可選用多種,在這裡
我們選用(A AND 0x7F)OR B。注意,白色不能產生滲透。
//參數說明:
//hDIB -位元影像控制代碼
//pPal -位元影像調色盤
//xDest -顯示位元影像的左上方x座標
//yDest -顯示位元影像的左上方y座標
void DrawSemiTransparentBitmap(CDC *pDC, int nXDest, int nYDest, HGLOBAL hDIB,CPalette *pPal)
BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;
int nColors = bmInfo.bmiHeader.biClrUsed ? bmInfo.bmiHeader.biClrUsed :
1 << bmInfo.bmiHeader.biBitCount;
int nWidth = bmInfo.bmiHeader.biWidth;
int nHeight = bmInfo.bmiHeader.biHeight;
LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
CDC memDC;
memDC.CreateCompatibleDC( pDC );
CBitmap bmp;
bmp.CreateCompatibleBitmap( pDC, nWidth, nHeight );
CBitmap *pOldBitmap = memDC.SelectObject( &bmp );
if( pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE&&nColors<256)
CPalette *pOldMemPalette = memDC.SelectPalette(pPal, FALSE);
memDC.RealizePalette();
::SetDIBitsToDevice(memDC.m_hDC, 0, 0, nWidth, nHeight, 0, 0, 0, nHeight, lpDIBBits, (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);
CDC maskDC;
CBitmap mbm;
maskDC.CreateCompatibleDC(pDC);
mbm.CreateCompatibleBitmap(pDC, nWidth, nHeight);
maskDC.SelectObject(&mbm);
maskDC.FillSolidRect(CRect(0, 0, nWidth, nHeight), RGB(0x7F, 0x7F, 0x7F));
pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &maskDC, 0, 0, SRCAND);
pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &memDC, 0, 0, SRCPAINT);
memDC.SelectObject(pOldBitmap);
}
如何畫透明位元影像
在豐富多彩的軟體世界中,位元影像的處理技術尤為重要。透明位元影像的顯示作為
一種常用的影像處理方法,被用在眾多的軟體中。其基本原理,也就是將一幅需
要透明顯示的位元影像(其透明色為已知),製作出二幅需要的位元影像A與B,其中A為除
透明色外均填充為黑色,B為把透明色填充為黑色其餘色不變,再用指定光柵操作
將兩幅位元影像合并,可形成透明位元影像。
建立過程如下:
1、使用透明色背景,將位元影像拷貝到記憶體屏蔽位元影像中。
2、利用與白色相與不變色,與黑色相與成黑色的原理,將記憶體位元影像的的背景設定
成黑色、前景設定成白色,並將屏蔽位元影像用與操作拷貝到此記憶體位元影像中。形成位元影像B。
3、將顯示裝置背景設定為白色、前景設定成黑色,並將屏蔽位元影像用與操作拷貝到
顯示裝置中。形成位元影像A。
4、將記憶體位元影像用或操作拷貝到顯示裝置中。最終形成透明位元影像。
//參數說明:
//hDIB -位元影像控制代碼
//pPal -位元影像調色盤
//xDest -顯示位元影像的左上方x座標
//yDest -顯示位元影像的左上方y座標
//colorTransparent -透明色
void DrawTransparentBitmap( CDC *pDC, int nXDest, int nYDest,HGLOBAL hDIB, COLORREF colorTransparent, CPalette *pPal)
{
BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;
int nColors = bmInfo.bmiHeader.biClrUsed ? bmInfo.bmiHeader.biClrUsed :1< int nWidth = bmInfo.bmiHeader.biWidth;
int nHeight = bmInfo.bmiHeader.biHeight;
LPVOID lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors);
CDC memDC,maskDC;
memDC.CreateCompatibleDC( pDC );
CBitmap bmp;
bmp.CreateCompatibleBitmap( pDC, nWidth, nHeight );
CBitmap *pOldBitmap = memDC.SelectObject( &bmp );
if( pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE&&nColors<256)
CPalette *pOldMemPalette = memDC.SelectPalette(pPal, FALSE);
memDC.RealizePalette();
::SetDIBitsToDevice(memDC.m_hDC, 0, 0, nWidth, nHeight, 0, 0, 0,
nHeight, lpDIBBits, (LPBITMAPINFO)hDIB, DIB_RGB_COLORS);
maskDC.CreateCompatibleDC(pDC);
CBitmap maskBitmap;
maskBitmap.CreateBitmap( nWidth, nHeight, 1, 1, NULL );
maskDC.SelectObject( &maskBitmap );
memDC.SetBkColor( colorTransparent );
maskDC.BitBlt( 0, 0, nWidth, nHeight, &memDC, 0, 0, SRCCOPY );
memDC.SetBkColor(RGB(0,0,0));
memDC.SetTextColor(RGB(255,255,255));
memDC.BitBlt(0, 0, nWidth, nHeight, &maskDC, 0, 0, SRCAND);
pDC->SetBkColor(RGB(255,255,255));
pDC->SetTextColor(RGB(0,0,0));
pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &maskDC, 0, 0, SRCAND);
pDC->BitBlt(nXDest, nYDest, nWidth, nHeight, &memDC,0, 0, SRCPAINT);
memDC.SelectObject( pOldBitmap );
}
畫透明位元影像通常的方法是使用遮罩。所謂遮罩就是一張黑白雙色的位元影像,他和
要透明的位元影像是對應的,遮罩描述了位元影像中需要透明的部分,透明的部分是黑色的,
而不透明的是白色的,白色的部分就是透明的部分。
假設圖A是要畫的透明位元影像,圖B是遮罩,圖A上是一個大寫字母A,字母是紅色的,背
景是黑色的,圖B背景是白色的,上面有一個黑色的字母A和圖A的形狀是一樣的。
比如我們要在一張藍天白雲的背景上透明地畫圖A,就是只把紅色的字母A畫上去。我
們可以先將圖B和背景進行與操作,再把圖B和背景進行或操作就可以了。
用VC++ MFC實現的代碼如下:
void CDemoDlg::OnPaint()
CPaintDC dc(this);
Cbitmap BmpBack,BmpA,BmpB,*pOldBack,*pOldA,*pOldB;
BmpBack.LoadBitmap(IDB_BACKGROUND); // 載入背景圖
BmpA.LoadBitmap(IDB_BITMAPA); //載入圖A
BmpB.LoadBitmap(IDB_BITMAPB); //載入圖B
CDC dcBack,dcA,dcB; //聲明三個記憶體DC用於畫圖
dcBack.CreateCompatibleDC(&dc);
dcA.CreateCompatibleDC(&dc);
dcB.CreateCompatibleDC(&dc); //把這三個記憶體DC建立成和PaintDC相容的DC
pOldBack=dcBack.SelectObject(&BmpBack);
pOldA=dcA.SelectObject(&BmpA);
pOldB=dcB.SelectObject(&BmpB); //把三個位元影像選入相應的DC
dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY); //畫背景
dc.BitBlt(0,0,48,48,&dcB,0,0,SRCAND); //用與的方式畫遮罩圖B
dc.BitBlt(0,0,48,48,&dcA,0,0,SRCPAINT); //用或的方式畫遮圖A
dcBack.SelectObject(pOldBack);
dcBack.SelectObject(pOldA);
dcBack.SelectObject(pOldB); //從記憶體DC中刪除位元圖
你會看到紅色的字母A透明地畫在背景上了。
用遮罩的方法必須事先做好遮罩,遮罩和位元影像大小一樣等於多消耗一倍的資源,
比較浪費。還有一種畫透明位元影像的方法,基本原理是一樣的,只是不用事先做好
遮罩,根據需要動態產生遮罩,但是要求需要透明的位元影像必須指定一種透明色,
凡是這個透明色的地方則畫成透明的。
用VC++ MFC實現的代碼如下:
/*
這是一個用來畫透明位元影像的函數
CDC *pDC 需要畫位元影像的CDC指標
UINT IDImage 位元影像資源ID
Crect &rect 指定位元影像在pDC中的位置
COLORREF rgbMask 位元影像的透明色
*/
void DrawTransparentBitmap(CDC *pDC, UINT IDImage,Crect &rect, COLORREF rgbMask)
{
CDC ImageDC,MaskDC;
Cbitmap Image,*pOldImage;
Cbitmap maskBitmap,*pOldMaskDCBitmap ;
Image.LoadBitmap(IDImage);
ImageDC.CreateCompatibleDC(pDC);
pOldImage=ImageDC.SelectObject(&Image);
MaskDC.CreateCompatibleDC(pDC);
maskBitmap.CreateBitmap( rect.Width(), rect.Height(), 1, 1, NULL );
pOldMaskDCBitmap = MaskDC.SelectObject( &maskBitmap );
ImageDC.SetBkColor(rgbMask);
MaskDC.BitBlt( 0, 0, rect.Width(), rect.Height(), &ImageDC, 0, 0, SRCCOPY );
ImageDC.SetBkColor(RGB(0,0,0));
ImageDC.SetTextColor(RGB(255,255,255));
ImageDC.BitBlt(0, 0, rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND);
pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &MaskDC, 0, 0, SRCAND);
pDC->BitBlt(rect.left,rect.top,rect.Width(), rect.Height(), &ImageDC, 0, 0,SRCPAINT);
MaskDC.SelectObject(pOldMaskDCBitmap);
ImageDC.SelectObject(pOldImage);
}
void CDemoDlg::OnPaint()
{
CPaintDC dc(this);
Cbitmap BmpBack,*pOldBack,;
BmpBack.LoadBitmap(IDB_BACKGROUND);
CDC dcBack;
dcBack.CreateCompatibleDC(&dc);
pOldBack=dcBack.SelectObject(&BmpBack);
dc.BitBlt(0,0,100,100,&dcBack,0,0,SRCCOPY);
DrawTransparentBitmap(&dc,IDB_BITMAPA,Crect(0,0,48,48),RGB(192,192,0));
dcBack.SelectObject(pOldBack);
}
//XOR 方式
http://hi.baidu.com/goldenypb/blog/item/39fa8cddf769f0355882dd6c.html
首先是異或運算:
異或的運算方法是一個二進位運算:
異或的運算方法是一個二進位運算:
1^1=0
0^0=0
1^0=1
0^1=1
兩者相等為0,不等為1.
這樣我們發現交換兩個整數的值時可以不用第三個參數。
如a=11,b=9.以下是二進位
a=a^b=1011^1001=0010;
b=b^a=1001^0010=1011;
a=a^b=0010^1011=1001;
這樣一來a=9,b=11了。
再就是sgyxslsc先生的這篇文章(原文出處:http://sgyxslsc.spaces.live.com/blog/)
異 或運算有這麼個特點:用一個數B與另一個數A進行兩次異或運算,那麼A的值保持不變。可以設想一下,在顯示位元影像的時候,圖片中的位與欲顯示的位元影像的地區進 行兩次異或操作時,欲顯示的位元影像的地區不會產生任何變化。也就是說,如果將圖片中的背景色與欲顯示的位元影像的地區進行兩次異或運算,那麼背景色就被過濾掉 了,但同樣,位元影像中非背景的內容也被過濾掉了。
現在我們這樣,第一次異或運算之後,加這樣一個操作。把位元影像中的主要內容與0作與運算,而背景地區與1作與運算。這樣位元影像的主要內容區變為都變為0,而背景地區還是原映像與視窗背景的異或值。這樣我們再進行第二次異或操作,位元影像的主地區與0異或保持不變,而背景地區被消除了。
而執行這個操作需要我們建立一個“面具”圖片,這個圖片只有兩種顏色:黑和白。黑色的RGB值全部是0組成,白色的RGB值肯定是全部由1組成。而這張圖 片的特點就是 主要內容的值都是0(黑),背景色都是1(白)。在兩次異或的中間用這個圖片以與的方式在顯示地區顯示就可以了。
在BitBlet函數中,“異或”對應於“SRCINVERT”,“與”對應於“SRCCOPY”。下面是書中的程式:
void CTransBitmapDlg::OnPaint()
{
CDC *PDC = GetDC();
CDC dcImage, dcTrans;
//裝入欲顯示的位元影像
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAPID);
//取得位元影像的詳細資料
BITMAP bm;
bitmap.GetBitmap(&bm);
int nWidth = bm.bmWidth;
int nHeight = bm.bmHeight;
//建立與裝置環境相關的位元影像,dcImage中用來裝入欲顯示的位元影像
//dcTrans中用來裝入過濾背景色的位元影像
dcImage.CreateCompatibleDC(pDC);
dcTrans.CreateCompatibleDC(pDC);
CBitmap *pOldBitmapImage = dcImage.SelectObject(&bitmap);
// 建立與欲顯示的位元影像等大的單色位元影像,用來過濾背景色
CBitmap bitmapTrans;
// “面具”就在這步做好
bitmapTrans.CreateBitmap(nWidth, nHeight, 1, 1, NULL);
CBitmap *pOldBitmapTrans = dcTrans.SelectObject(&bitmapTrans);
//強行將欲顯示的位元影像的背景色設定為白色,以方便建立dcTrans, 這步很重要。如果你的
//位元影像的背景色不是白色的,你只需要修改這一步的參數。
dcImage.SetBkColor(RGB(255, 255, 255));
dcTrans.BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCCOPY);
//在dcTrans中,背景色變為白色,而主要映像的各種色彩都被設定成黑色
//顯示“面具”,這一步主要目的的主要目的在於讓你更能夠明白“面具”的真面目
pDC->BitBlt(0, bm.bmHeight, nWidth, nHeight, &dcTrans, 0, 0, SRCCOPY);
pDC->BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
//該步驟將源位元影像與目的地區域進行異或運算
pDC->BitBlt(0, 0, nWidth, nHeight, &dcTrans, 0, 0, SRCAND);
//通過dcTrans與目的地區域進行“與”操作,建立了與主要映像輪廓一致的黑框以保護圖片的主要內容不會改變
pDC->BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
//用黑框位元影像與源位元影像進行黑色異或運算,將源碼位元影像中有色彩(除背景色以外)
//的內容還原。在這步,背景色經過與目的地區域進行了兩次異或運算,背景色已經被過濾
}
}
其中CreateBitmap不明白,怎麼利用它就建立出白底黑面的位元影像了,又看了瘋狐先生的文章,一切都明白了:
函數功能:該函數建立一個帶有特定寬度、高度和顏色格式的位元影像。
函數原型:HBITMAP CreateBitmap(int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, CONST VOID *lpvBits);
參數:
nWidth:指定位元影像寬度,單位為像素。
nHeight:指定位元影像高度,單位為像素。
cPlanes:指定該裝置使用的顏色位面數目。
cBitsPerPel:指定用來區分單個像素點顏色的位元(位元數目)。
lpvBits:指向顏色資料數組指標。這些顏色資料用來置矩形地區內像素的顏色。矩形地區中的每一掃描線必須是雙位元組整數倍(不足部分以0填充)。如果該參數為NULL,那麼就表示沒有定義新位元影像。
備忘:在建立完位元影像後,可以通過使用SelectObject函數把它選入到裝置環境中。儘管函數CreateBitmap可以用來建立彩色位元影像, 但由於效能方面的原因,應用程式使用CreateBitmap函數來建立單色位元影像,建立彩色位元影像應該使用函數 CreateCompatibleBitmap。當由CreateBitmap建立而返回的彩色位元影像被選入到裝置環境時,系統必須確保選入進去的裝置環境 格式與位元影像匹配。由於函數CreateComapatibleBitmap擷取裝置環境,所以它返回的位元影像與指定的裝置環境有相同的格式。所以對Select的後續調用都要比從CreateBitmap函數建立返回的彩色位元影像調用快。
如果位元影像是單色的,那麼對於目標裝置環境而言,0代表前景顏色,而1表示背景顏色。
如果應用程式將nWidth或nHeight參數設為0,那麼函數CreateBitmap返回的是只有一個像素的單色位元影像控制代碼。當不再需要位元影像時,可調用DeleteObject函數刪除它。
Windows CE:參數cPlanes必須是1。