最近在用GDI+做个东西,想实现两幅带有Alpha通道的图像(比如PNG图像)的透明渐变。方法很简单,用双缓冲的办法,但是不同的是,先把下面那幅图,也就是下图的图B,先画到内存Graphics中,然后再用设置ColorMatrix的方法,在B图上按一定透明度混合A图,最后直接把整个内存位图一起输出到屏幕上!

AlphaMixAB

实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Bitmap* CreateTransGradientMemBmp(Bitmap *pBkgndBmp, 
	Bitmap *pFgndBmp, int nWidth, int nHeight, REAL rTransparence)
{
	Bitmap* pMemBmp = new Bitmap(nWidth, nHeight);
 
	static ColorMatrix clrMatrix = 
	{
		1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 0.0f, 0.0f, 1.0f
	};
 
	clrMatrix.m[3][3] = rTransparence;
 
	Graphics memGraph(pMemBmp);
	memGraph.DrawImage(pBkgndBmp, 0, 0);
	ImageAttributes imgAttr;
	imgAttr.SetColorMatrix(&clrMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);
	memGraph.DrawImage(pFgndBmp, Rect(0, 0, nWidth, nHeight),
		0, 0, nWidth, nHeight, UnitPixel, &imgAttr);
 
	return pMemBmp;
}

 

理论上的情况,这种方法的效果应该是很好的!可是设置一个15ms的定时器来画的时候,过几秒就闪一下,而且还会引起整个父窗口的重绘,我完全无语了!以前就听说过GDI+很慢,没想到这么慢,唉!

但是,无意中搜索到本文的读者真的有福了!因为我折腾了两天终于找到了解决办法!

现在说说我实现的思路:

snap2

如上图,首先需要得到(父)窗口背景图的内存DC,只需要知道此窗口的DC和需要截取的区域就行了,这个原理和截图软件的原理是一样的,只要窗口不带透明度,此法的显示效果还是比较好的!为什么需要用窗口背景图先与待处理的图像混合呢?因为透明渐变两幅不同的图像,如果只是直接在屏幕DC上直接Alpha混合的话,势必有可能存在下面那幅图像没被上面那幅图像挡住的情况,就很影响显示效果了!而创建成长宽相等的内存图像,就正好可以挡住下面那幅图像,而背景又是(父)窗口的背景,这样给用户的感觉就会是,下面那幅图像渐渐变淡消失,而上面那幅图像渐渐变得清晰。

这样得到C、D两幅内存图像后,再用类似之前讲的办法,但是这回应该用GDI的技术了。将C、D AlphaBlend到新的内存DC上。当然也可以直接在其中一个中处理,但是如果要反复使用这两幅图的话,就应该用新的内存DC了。

最后不用说了,直接BitBlt到屏幕上

最后罗嗦一句,虽然想起来步骤比GDI+的方法多多了,但是屏幕根本不会闪烁,实际效果要好很多!

相关代码(函数就懒得解释了,大家看代码就懂了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
CDC* GetParentBkgnd(CWnd *pWnd, CPoint ptDrawStart, int nWidth,
		int nHeight)
{
	CDC* pDC = pWnd->GetDC();
	CWnd* pWndParent = pWnd->GetParent();
	CDC* pParentDC = pWndParent->GetDC();
 
//得到图像绘制开始点在父窗口上的坐标
	pWnd->ClientToScreen(&ptDrawStart);
	pWndParent->ScreenToClient(&ptDrawStart);
 
//初始化内存DC
	CDC* pMemDC = new CDC;
	pMemDC->CreateCompatibleDC(pParentDC);
	CBitmap memBitmap;
	memBitmap.CreateCompatibleBitmap(pParentDC, nWidth, nHeight);
	pMemDC->SelectObject(&memBitmap);
 
//拷贝父窗口背景到内存DC
	pMemDC->BitBlt(0, 0, nWidth, nHeight, pParentDC, ptDrawStart.x, 
		ptDrawStart.y, SRCCOPY);
 
//释放	
	pWndParent->ReleaseDC(pParentDC);
	pWnd->ReleaseDC(pDC);
 
	return pMemDC;
}
 
CDC* CreateMixDC(CDC* pParentMemDC, Bitmap* pBmp)
{
	int nWidth = pBmp->GetWidth(),
		nHeight = pBmp->GetHeight();
 
//照原样先拷贝一份
	CDC* pMemDCDst = new CDC;
	pMemDCDst->CreateCompatibleDC(pParentMemDC);
	CBitmap memBitmap;
	memBitmap.CreateCompatibleBitmap(pParentMemDC, nWidth, nHeight);
	pMemDCDst->SelectObject(&memBitmap);
	pMemDCDst->BitBlt(0, 0, nWidth, nHeight, pParentMemDC, 0, 0, SRCCOPY);
 
//画上图像
	Graphics memGraph(pMemDCDst->m_hDC);
	memGraph.DrawImage(pBmp, 0, 0, nWidth, nHeight);
	memGraph.ReleaseHDC(pMemDCDst->m_hDC);
 
	return pMemDCDst;
}
 
void AlphaGradientDraw(CWnd* pWnd, CDC* pFgndMemDC, CDC* pBkgndMemDC,
		CPoint ptDrawStart, int nWidth, int nHeight, int nTransparence)
{
	BLENDFUNCTION bf;
	bf.BlendOp = AC_SRC_OVER;
	bf.BlendFlags = 0;
	bf.AlphaFormat = 0;
	bf.SourceConstantAlpha = nTransparence;
 
	CDC* pDC = pWnd->GetDC();
//创建内存DC
	CDC memDC;
	memDC.CreateCompatibleDC(pDC);
	CBitmap memBitmap;
	memBitmap.CreateCompatibleBitmap(pDC, nWidth, nHeight);
	memDC.SelectObject(&memBitmap);
 
//在内存中先画好
	//memDC.BitBlt(0, 0, nWidth, nHeight, pDC, 0, 0, SRCCOPY);
	memDC.BitBlt(0, 0, nWidth, nHeight, pBkgndMemDC, 0, 0, SRCCOPY);
	AlphaBlend(memDC.m_hDC, 0, 0, nWidth, nHeight, pFgndMemDC->m_hDC,
		0, 0, nWidth, nHeight, bf);
 
//直接输出
	pDC->BitBlt(ptDrawStart.x, ptDrawStart.y, nWidth, nHeight, &memDC, 
		0, 0, SRCCOPY);
 
	pWnd->ReleaseDC(pDC);
}