|
|
周鸣扬
) F. ^ s/ T/ f4 _2 a
3 t: z, W7 S2 z1 ~# w, k! Z 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?; J3 r) h1 }* U
( q, V: B& w | ^9 G l! r5 \- Q% x+ A
解决方案
3 n1 U1 H3 D1 ]4 X7 ?
% h1 I3 M; A% @1 l) z: d- C2 A
J6 q2 [+ {/ C0 \' P 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:8 W0 N. r; D& w7 x( g& v! F
7 z+ T6 v# t% A8 D' K Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
+ L3 |! i* W2 C0 g, v
2 M2 ]/ B% k( @$ P7 A# s: @ Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 2 d0 N- S _% h5 U+ a& l6 V3 u1 W7 p
4 w ^ P2 @# X- D. p/ g
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
$ c* W7 {$ C; Z6 S- h% T, A
( q, \$ U& U5 S v 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。; N8 ?. P& e* x# h3 }% S. f' C
: ^ l. {" }* i) ~; J/ R- o 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:! J# Y$ Q1 g. c' ]' w$ w
3 x H6 P7 V% `3 Q( i BOOL AlphaBlend(7 t& s6 D8 d) H3 @0 [2 o/ Z
; n- g, M! t6 }2 a
HDC hdcDest,
( t# ]5 X2 X& R5 L& ?. q# w# ~% X# w- f& {
// 目标设备环境句柄 @9 H y. ]6 z9 A/ k0 f- S
9 Z4 C) J5 p% g" T q/ k
int nXOriginDest,
J6 G" H7 @, X! z# n( _9 e% G- F: K0 t4 _3 D3 [2 N0 [
// 目标坐标x
) Z0 ~* c4 z3 [4 P# Y. }6 @/ B. P$ Y& x2 z9 w! G
int nYOriginDest,
) V4 O% g. K' r! O& n
5 P+ l1 }0 h8 S7 ] // 目标坐标y
1 e. I% s1 w1 D. @, }. J' C7 C. [+ u( Z6 e2 _( \0 t
int nWidthDest, ' _: k! s3 b$ G3 G8 n
) \2 e' X/ K3 L- n% W
// 目标宽度. y6 `0 D% u. Z) v$ N
5 f5 y. X$ ?9 j int nHeightDest,
7 }) Q$ o# f! O, L1 q3 q. R, P4 Y9 J3 ^, G3 C6 K
//目标高度
% g3 ]" j1 h- e; b- F) e2 m+ m# Q6 B- Y( Q x. t& e( ^0 n
HDC hdcSrc,
, @, U0 p6 e' u) X0 }1 u# E7 A3 O+ |' U- d: k! q/ F9 p* {9 K
//源设备环境句柄 e6 \ Q* Y* }$ H) F% v3 d6 ~
! Y7 m( j$ |2 n) m# B1 w' {8 n9 v! ` int nXOriginSrc, - T9 u, r: R% v% Y
! p9 \; Z" @8 s* p; f9 K
// 源坐标x, n* z! {6 m& A4 t1 Y
% v; d1 g- R9 e; w- g int nYOriginSrc,
. W4 E i, M8 H0 L8 S
$ J. @9 _# t6 c8 X& J // 源坐标y8 t- I9 o& V) p5 ]
9 H( k" b( @ P+ E: N. s N
int nWidthSrc,
4 B* j" q4 i! u. c2 i
% c" F s4 M6 W3 _, ]* Q0 ~3 y //源宽度6 |: Z$ v' m" G+ A" L0 @* O5 ~8 K
?3 k4 _ K- {3 F0 f
int nHeightSrc,
" ?1 S5 Z, k, ~% W4 C/ a! N8 a g& D g ~' u9 A# e& |
//源高度/ ]6 P h- [, b& n" G# h
1 g2 p# d4 e8 i( ^ e( a BLENDFUNCTION blendFunction . s8 v9 n* k5 ^7 Y
7 d. h3 u1 ^4 B) a/ p
// 合成方式具体数据结构
4 f: L3 M: O4 c* C4 I0 b
' C# \; _) V, a0 r+ _8 S; [ Y );8 ^8 Y. p% F# S) _5 a, @
% F7 g4 l6 i4 P( t" K$ [ BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
6 G- [+ ]- R* T
# P# i* ^ I, ?6 f3 w) D1 f, X4 | typedef struct _BLENDFUNCTION {
+ t. r8 n5 N5 _" ]! u) G; g/ K- o
5 _3 z+ r% E: t9 D( k) q BYTE BlendOp;! w& \6 X( h+ ?7 j2 I0 L. ^ V: u
( J, f# F6 b4 }, f, m1 ?! c# z BYTE BlendFlags;//必须为零
- r8 h1 k4 {+ S' W* \2 n- |1 Z+ u5 ?1 K7 D
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
" L* x3 J0 B% N- P; m8 u3 S0 C* P; P* a
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
6 n) Y# s. m5 s- b9 b
% V3 p" [- D0 k! V }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
2 U% ~! v, s X; Z- u
" G, {" o# m/ ?0 r `5 y6 m 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
0 E s6 z; J1 K/ T7 a! G* X& z
* }" O4 X3 d) v9 @* G( g& G* I. _! o9 N' b' U. m& Y; {( S" L
编程实现
6 d- c6 e! o+ x/ Q, l" f, L2 l: ~/ @3 U. \6 O% @, c, q7 K
0 r$ Z- e7 ^/ l1 V3 x* `( ~ 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
1 A: Q6 [4 N. U9 }
, q2 N9 Y* n$ B 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
5 u9 D0 I" u* D( F( Z" E9 n% G" F$ K0 R' Z' z: B4 s
class CWipeImageDlg : public CDialog
- W5 y7 O" L$ i b% d, e
1 k, x7 g. Y8 W) `4 D {
8 W5 m3 n( P$ w Z6 a5 s* b" E/ W9 i7 O
// Construction
0 \( g: M" j0 E! [1 H& ^# \2 n% h( h8 ^5 P3 m! O/ I
public:
& ?# F# I5 W- z/ z4 S; b
+ d: y1 }1 W6 r BLENDFUNCTION m_bf;
$ q, E+ Q' t: ] z) L( c1 [" P; A4 e; E
CBitmap cross,lantern;; |! E$ `4 I4 N5 l" ?
' [+ F# y" m. Z' d; s/ p
BITMAP bmp;
6 g) x& x& z& Q, s1 ]8 v. P6 n- c9 J* x8 o0 Q" |% I, r
int bmpWidth,bmpHeight;0 Y- t* ]" Y+ x
# H, @9 S3 d% L3 o# ?, [$ [" _/ x
CDC dcForCross,dcForLantern;
0 S4 ~; y2 C# o' L, x4 ^- |. p a( w8 y' s$ e7 }
CDC? dc;- X2 F/ V. {$ z: e8 O% Y- P9 t1 e9 t
. ]+ Q- k2 [7 Y. V
BOOL bShowLantern;! \8 N8 F; K$ N5 Q! ~ ]% u* S$ @5 j
2 A# E7 I1 ^& l: ?9 i ………(系统自动生成部分)! R! X+ a0 [, U9 C; C
& e4 W0 |4 H! u( i }; 9 ?1 |3 { r* y0 T5 y+ m3 t
1 v/ F; ~; S, `; G5 ?% I3 ^
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
) @4 P3 O0 X$ f5 S9 E4 h: _9 ], k
BOOL CWipeImageDlg::OnInitDialog()
6 b# X/ I' a) \0 {# ~# j5 L
6 w- j4 m0 A' Q4 ~- ~. Z! K& u. @# | {
$ ?7 p4 L: ^, r% I4 J$ M4 F
, K2 S7 M3 |9 a( S+ A m ………(系统自动生成部分)
/ Z7 Y/ W& I0 z+ C+ x4 s6 q# T* Y4 F: F3 _0 A+ V- `+ S Z2 J
// TODO: Add extra initialization here
8 h' ]5 s/ c) u" Q3 T/ r/ a
9 M" p- G1 I Q" B; u //初始化全局成员变量
" M; D. Z8 S7 Z) h" x5 n7 @# p1 S. `* _0 o2 J: h+ c0 x
this->bShowLantern=TRUE;
0 ?. k0 k$ s( J: Y! n" Q6 R% J; l! a3 b( |: A
m_bf.BlendOp = AC_SRC_OVER;1 D8 P( y! x# ^4 d
- V, n/ ~; n9 f W) L. b8 }1 v m_bf.BlendFlags = 0;1 Y4 g# x, _! N0 n
+ ~& a) ^8 j5 V: n8 |; W
m_bf.SourceConstantAlpha =10;
. l3 ~! S o7 X O( e2 y
3 L/ k1 K9 X8 O m_bf.AlphaFormat = 0;
- y" O. F8 k) X" M: Q5 R. ^
5 C0 {, G' K( ]7 B. u //为节约篇幅,以下代码中略去对操作不成功的处理代码
# h5 }- q1 `& M9 H
& E: \: |! {+ @ if(!cross.LoadBitmap(IDB_CROSS))
+ c+ _" Z. `: T; R9 h/ O& B, {2 K6 m# w( t7 L- y/ M' w8 }2 A
{! F* z% C. F2 V: [+ ?
( H, M1 v) Q9 d- z3 l3 g
AfxMessageBox("装载位图出错!");& [/ d' z" Q& n' n" t
( d+ T2 S" y* w; n0 X; q2 v return FALSE;( _/ Q) f2 W4 @. C* O1 ], P
# I( i* Y$ |, M: Y6 M; |
}
+ j l, I# q. r5 G& p8 l9 `7 n1 s- p8 u, L7 J
cross.GetBitmap(&&bmp);* P& j/ L- U: m2 h3 P* W
. D H& L6 m) ]7 | lantern.LoadBitmap(IDB_LANTERN);, i ?; K. g/ l+ T( h$ y
0 {; @6 _' R3 W, w% l7 B x* l cross.GetBitmap(&&bmp);# N3 R- d, |5 q1 W3 \( o
* R( V8 c$ A* e7 c7 T! p6 a //获得位图的大小信息2 u- G" j* F, O8 g( R' P5 E* G
U' ]+ W K: c+ e6 F- c# n% R
bmpWidth=bmp.bmWidth;4 m( V$ A; z& C
) P! ?& e5 O: b% z* F7 P. K bmpHeight=bmp.bmHeight;' t! _' ~9 O/ F( c/ Y
' ~; P, F" Z6 r/ i i) V0 p
dc=this->GetDC();
/ T! v1 r) b# S+ q3 P
* z. b: c5 O, Q* G. ^9 _ dcForCross.CreateCompatibleDC(dc);
D1 ^# J3 Z. ?/ F, P4 E4 I6 A8 s* X R; S
dcForLantern.CreateCompatibleDC(dc);
/ \* Z% g4 w! b4 \9 }/ x2 c; S' U, D8 J- ]
//将位图装入设备环境句柄' l% P Q9 D* S" l% x( C; _0 v5 a
9 z- R; |5 u$ o& r8 ~: s; S! Z dcForCross.SelectObject(&&cross);3 z! ?8 a. s& a. j Q+ a8 b
2 h9 _; Z! ?! X. B
dcForLantern.SelectObject(&&lantern);, [7 q! X8 R7 Q5 [: E
_ D& G! D3 v, H/ I1 t
//打开计时器6 k, N: A) u6 p' Z; r
2 m, U Z& q* s0 }$ }! ~) c6 S SetTimer(1000,50,NULL);
% W- }- N: V/ B. ~# l
% |9 J( C8 B) _0 [$ s0 Q return TRUE;
1 ?6 z1 w# L' ?4 R# }8 K- h, I. s, [- }& r5 [9 X( I" a/ n4 k
}, z" B8 {% a i( r& Y
8 D+ q+ V1 O: p2 U
void CWipeImageDlg::OnTimer(UINT nIDEvent)
5 {5 [6 v/ a* i \
8 _3 H; o4 c) U5 V0 D! c; \ {# }; F- j$ i: F! B$ l, X
" X+ o& s: \3 b5 |" s( f2 O. |
//图片透明度每次递增5点5 q) V3 h& g7 N4 p* j$ S1 |2 \
1 v2 W' `6 g1 W8 m
m_bf.SourceConstantAlpha+=5;. _# t& R# O$ Z. \0 d
, S- S, ~% J) M! T6 c //当第一幅图片完全可见之后,显示另一张图片
! s. a: r3 d' U3 y/ c5 k- Q3 K0 D m- C: M
if(m_bf.SourceConstantAlpha>=200)7 N* i+ E1 m! u. Q! O& I) z: A
; C) {4 o4 z* C
{
! G$ q% `) H V; L
0 K; S( R9 w M$ b: X& k+ y n m_bf.SourceConstantAlpha=10;
g. B, E( E% G. i; F; P( A1 p0 \, Q) f8 C
//将bShowLantern做为显示标志,确认应该显示哪一张图片
5 b# h) S' E( @9 ^
# q" `, a4 ?' E/ ] bShowLantern=!bShowLantern;
+ ^& Y% k; Q5 y, `$ D
$ ~2 k: d6 |# J! c& F }4 Y w- A9 U' z# f
# e5 Z* S$ T: W! R5 p if(bShowLantern)
( D& S8 s7 D! l8 E
, |- ]& t O. T9 [' D5 X' h2 g! p {: Z# l. [( N- N2 B
" J- B. L9 s9 k7 Z6 f
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);2 |0 C' _8 K4 C7 P. a
, |7 d) e% f8 S }+ X$ w& n Z( H" W6 l
$ {% q3 ]2 {$ \7 z
else
7 A$ `7 _5 h8 N% Q0 z0 m) b) G( z. k5 z' E
{
% D4 v u5 O9 R% y8 P# I) b. F2 i t: r% O* ?! ]
//按透明度递增的方式显示“背景”图片
' O1 L" K5 U% h, N7 k; E- M6 e' S# }* ]( i; F N
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
( \ M6 X0 [2 C1 n$ ^* J" Y; j
2 S) f$ z6 h+ P$ V1 O7 j! c }
% {6 Y8 ~- z' ]% e6 \ h4 t
0 J' |$ D9 _8 S CDialog::OnTimer(nIDEvent);
! C; v4 s6 [( I% I, ]& s" ~* }! J
}
- h) o# I: N( L1 ^
; t" C1 Z/ v0 _# B. j9 E6 c* _7 S1 t' G- O2 R }* W% h
编译说明* G2 q* c7 B8 y9 [- s2 N+ k
% `# d! S: X l5 @" c. y/ T' P
" C/ j) p( A: D, { 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|