|
|
周鸣扬
' \, A$ N1 i3 p9 B4 d9 [4 U4 ? `8 m: P+ u! C! m
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?9 N* A6 @3 t+ j1 x4 K" @
! a: C6 c. M6 V4 I: X: y) {( @# r; ^
( h6 ~3 x8 `' S M* m* i, y. n 解决方案, n* B q$ m0 ^# |" [% n
+ o, N1 q$ O, b; ^% x4 g
/ m3 h" Q; J5 A3 m6 c# E 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:* A7 w; v) q5 |- G8 W
- m& A8 n0 T- U Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
& }4 m+ z" f6 A
' N% S- {3 y2 m1 ^! m$ U3 s Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ( B" j8 r( q3 x8 q$ ]" C0 ^& b/ g
4 R- n. p( }9 c
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
# U0 Y( u* o2 ]2 J4 \
1 A& p- A ]8 g* Z5 F8 G8 k 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。4 \$ y* y: R4 G6 i# @( \* A
/ W" b; |9 T. v3 B5 u: N$ f! ` 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
1 U3 X3 H9 a4 z3 i2 g7 v9 b& A8 w
BOOL AlphaBlend(3 _* q: |9 ?! r* R) ~( q+ U
5 ~ U2 q6 }. R2 J4 c
HDC hdcDest, 2 \; c4 T4 ~0 J. ] J
" ]9 Q. P3 e g5 Y8 `6 g S
// 目标设备环境句柄
/ G2 }( c8 E: B, B9 o4 m+ H
6 G _& d4 G# D7 ^# l$ W9 f3 G int nXOriginDest, $ U; I& a) Y$ M# { H
2 F" r' ^4 u) Z3 ^
// 目标坐标x
6 J$ v" n, M' U/ m
/ D9 @8 F- ]( R int nYOriginDest,
2 J9 _6 r- u9 h% G" s3 D3 I
- F$ I( p, z7 n* q5 F8 o // 目标坐标y" p- k3 S; [# `4 H; H1 L! ?; _
4 b, `3 [& Q! k int nWidthDest,
" q. U0 l X5 e! s+ ?$ j
" G5 _) J6 c9 Y8 v* ^/ G // 目标宽度
, W% W( ?* _; b8 ~- W
7 I) Y0 u6 A7 b! I' l int nHeightDest,
! I& Q9 ?" u3 ^( t7 R5 ~+ G: p
1 V2 [7 |6 X! x7 ? //目标高度
, M5 o+ x& n- P( N
( S1 E% i1 T% t1 H HDC hdcSrc,
5 ~$ K: V+ S/ A0 l! P* M* ]0 i I$ C! g/ S, G3 C2 z
//源设备环境句柄% ]5 ^ c) k6 _1 J# _9 w8 c8 p& J
% }8 }- r2 C' E& L0 n/ r int nXOriginSrc, * a0 C! r6 \/ L
# M. w$ ~% G/ R3 r
// 源坐标x
X8 R; H- t. r1 o: y! p0 O
& q0 C+ R: A% m, T* f# j% ] int nYOriginSrc, ; j4 {' h0 r( v1 [- {7 j
$ V3 m7 O# k1 u8 }" U, i // 源坐标y: H- p9 R: q4 ^6 U% K% F
" l( `7 i) T6 m; v/ t8 e0 [' H6 N' r
int nWidthSrc,
+ D1 A( B2 o5 \* ~. S& t1 M, Q% V& ~; _" @
//源宽度7 b. D# d/ [4 @) w& x3 K) E
0 X% ~) _. }/ V6 x0 r int nHeightSrc, , K! E2 V3 l( T1 r; ~4 ^! i
* N' @# H1 { |- s% Q //源高度
# G4 {/ J& L/ ^. x. A C5 Q" D3 {0 x1 o3 L* s; ^
BLENDFUNCTION blendFunction
( L, i4 Y0 T+ P2 U) _$ F( D7 d/ Z0 d8 d% N3 @- {
// 合成方式具体数据结构; Q& {: v# K1 u
% b0 T( d2 Z1 p+ ~
);
# M# S0 A6 m9 E# O& l( U4 W3 t# L. L: H/ h6 V7 v+ K( h
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
T/ k. ~7 K5 e$ a
6 o9 j1 f& J& d& ?$ K typedef struct _BLENDFUNCTION {- L7 [4 \7 l8 O1 E
: I; E# z0 Q8 C* T& z# A4 h
BYTE BlendOp;: B- z0 X9 C* E2 V: c8 @) J
7 r: l) s3 q# t/ p; m. }
BYTE BlendFlags;//必须为零
1 }, s# }) n2 H+ X, O' b5 }3 Z9 l+ U& H; x5 n% J7 W+ [! d
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
/ h" y0 N4 q7 |6 b7 @& s. E% A( y+ h# ? n& U" j
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
. ?$ I& u. S6 E, e9 `6 r) _% k% r' ~: O/ I4 A6 \9 B* p
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
5 k. Q" F. i8 p+ }
) v% q7 \8 F' h2 w 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
s* B3 {$ | q: r) O; K8 ~' s+ r2 F+ C9 ?' }, R* l
4 d; k n. j5 H1 L- t1 u
编程实现
/ n" E# V; N: j
/ S* |& h M- V) J" q
+ ^6 @( C, p+ h* X z- G 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。2 y4 G- i' R0 c- l& I
+ w" b1 ?1 |4 L5 G7 b8 z
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
9 Z5 x% y. ?- v8 r$ t1 d/ ?
5 D% y6 c7 e- B* r- o I class CWipeImageDlg : public CDialog
3 W3 O) [. u3 e" V6 \( {, A4 [" x1 ?1 s! T2 `
{
; Q2 U; s: F ?& m( M
! ?" q" G/ F: ?! @# `9 J // Construction' _0 C, Z+ c$ U" t/ t
. T& @: l, y) x! F4 ~" ^3 U! z public:
7 J3 }! Z+ \; V T! d3 H8 t; O& [( C. v, q* F# C; q" ~! B/ L
BLENDFUNCTION m_bf;
: [# |; b1 o& D! d5 h% y
+ Q$ p# j6 M5 a$ w# I4 u CBitmap cross,lantern;4 O6 D9 F% C S1 r) ?
: Q4 O) g* m! u: E4 P, n g
BITMAP bmp;2 r$ m7 m2 b7 G. q5 m, b
# y7 d' n( A( u! L; ^
int bmpWidth,bmpHeight;( B% r# |: `) J/ ?$ e
, [+ d4 [- ~8 n& Y) P CDC dcForCross,dcForLantern;# `+ f; H% f& p8 I4 O4 Y
, T( p$ j/ D8 w0 R* d% o2 I CDC? dc;( M/ t' u4 }6 \) o. u
' v" k0 B2 o2 z+ R# ]+ K( N1 P BOOL bShowLantern;9 ?" {( ?6 V- f4 ?# @" Q
, k/ H7 E, p* f, G ………(系统自动生成部分)
; y+ N- k$ ]" O, I% O8 D- C# S8 }8 n2 I
};
+ c: f! u: x: Z& g! G* g6 o- o: N0 E' v; b* s
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:+ }8 p2 m* v# m! S$ S
; m4 V) H5 f: ?- R
BOOL CWipeImageDlg::OnInitDialog()
6 Z I" L) I4 K" P% t p
, j# Z& A2 r( s+ N% D {
* h! c' o& {$ a: ~! N D1 X# u4 O! M$ `( y
………(系统自动生成部分)
- G+ T! l# G2 Z8 G/ s: z5 f7 b% R6 z
// TODO: Add extra initialization here7 K+ K; ^% y! x* t5 `+ e$ [. i
! F9 g1 F2 _* L3 Z //初始化全局成员变量
o* f) t0 Q, V# S
# ]( \, C, K8 F' n8 y* M this->bShowLantern=TRUE;
7 e# _: v( ]$ l( h6 B. D. p4 V
1 O4 g% |# r" s% q( a m_bf.BlendOp = AC_SRC_OVER;
# S/ R( V1 b: c: i! u5 q( t$ x5 E9 J7 j% B# M2 O+ x/ U
m_bf.BlendFlags = 0;" s J/ S4 P$ H* \
# {; ?9 }( \; n# g. J; Q m_bf.SourceConstantAlpha =10;
$ Y# @2 m; o" z4 b0 y7 O# I1 i+ J
m_bf.AlphaFormat = 0;8 N1 M! P5 Z( }) w0 f
( A# A; \' r+ @, C U
//为节约篇幅,以下代码中略去对操作不成功的处理代码* h( U" N* X; C; z9 q/ d
0 Z& F" _5 G% i$ ~# p
if(!cross.LoadBitmap(IDB_CROSS))0 w0 u6 q0 b1 y2 ]2 O3 s
9 A. _, t8 d& q- l2 k
{
. ?- ?% o. P$ e! p g
/ o' ?+ n$ _; c( m9 |6 c* B5 Y AfxMessageBox("装载位图出错!");
i( C' v) V0 f' F6 p1 t8 Y5 j, n: | m/ ]
return FALSE;8 v5 ]0 @( f( d* ?5 V9 j
' v g; a: D- c5 S8 K w. f3 H }
' Z) K) \' L7 A! d/ f( W+ u# u- M. H0 A/ s) [
cross.GetBitmap(&&bmp);
4 @: r( B' v2 M' \. w" [1 }* b$ ]" e% K
lantern.LoadBitmap(IDB_LANTERN);* F' K- L6 }% n, F: B
) v% P, @# m- b: j( ] \4 `6 _, w! L
cross.GetBitmap(&&bmp);% F* y% x6 q, Z) w6 x
! L( x6 i% e% s //获得位图的大小信息* F. p: T) D5 `" h# N
3 I( R6 @2 U; s8 x
bmpWidth=bmp.bmWidth;
2 C2 P" r3 y+ h E! n1 x8 r8 s
9 B# a2 P& M. l, l" p9 N3 a' [ bmpHeight=bmp.bmHeight;
9 z' R1 }- M" @3 s4 v: d d/ D. b- Q/ M# t
dc=this->GetDC();+ J' ?0 C* q. a; _
3 F" q6 S% t% |( C+ W n. V dcForCross.CreateCompatibleDC(dc);
6 X* f8 T- V9 J) T
% h$ }4 [+ h. t dcForLantern.CreateCompatibleDC(dc);0 `2 J4 Q9 O$ q! b+ B) Y
3 [. k7 r. n9 O8 J
//将位图装入设备环境句柄+ R3 e! f$ T3 [9 t% Y! n
- @! { z7 N. w dcForCross.SelectObject(&&cross);
4 P1 X2 j8 |) ^; M; J. p' a5 z
: S/ f: D$ E, O9 w dcForLantern.SelectObject(&&lantern);
0 \1 {/ _7 i! p/ x
8 v/ i! T8 j" j1 u" X //打开计时器
0 u8 H7 N3 C) }, T1 @& ~
4 I4 u6 j# n9 l; U _1 W! x SetTimer(1000,50,NULL);2 b2 I& P2 @: ~5 Z" c2 j) h
# ? k( m: V( o) l return TRUE;
O* S% o+ h* R: ~9 ]0 w* G& V# I8 z# x) z9 f T8 X! S5 r
}
* g) w3 X; |9 b8 [5 y
4 n' W3 {' {( [9 X1 E2 w void CWipeImageDlg::OnTimer(UINT nIDEvent)
. N7 }+ l" `, g- g
8 I4 E& K+ j* p; R% @5 V! h d* ~ {
! [* G- `4 f) j4 ]# a6 P# e! g3 o0 ^* L% Y; {, S% Q7 m
//图片透明度每次递增5点
9 R, B. z$ }3 n$ C
* ?' ?1 @; ?' Y% t5 r+ m m_bf.SourceConstantAlpha+=5;
7 s9 O! o1 V; A/ N# t D9 a, p! a( U3 ^" A3 O9 M t1 N
//当第一幅图片完全可见之后,显示另一张图片
5 u% B* R: y0 @4 S- r" ]4 d
7 `( h9 q8 y( [3 p$ G( o+ i$ n" R if(m_bf.SourceConstantAlpha>=200)3 L1 c& A& |/ g+ b+ s
, ]- k4 K& C; K {# z% U# D3 O# E7 z
: i# O& ^' _6 e( U8 ^2 p3 v+ g
m_bf.SourceConstantAlpha=10;
6 X! u. Y! S/ C0 M% B; Y* c/ G& Y z4 [% K
//将bShowLantern做为显示标志,确认应该显示哪一张图片
0 f5 e, N6 ~. I3 x( h C+ d7 X/ _ h; [: C
bShowLantern=!bShowLantern;
% O7 b$ K3 Z3 F- \: u3 d( t/ j. p8 }, ^2 F
}7 D2 f/ \3 F9 A$ y
9 a1 A9 Y$ D! {. q% b
if(bShowLantern)
' W: {# I% o4 L5 T# q! z6 m# {8 }! Q$ e# w) P1 b
{
9 `, X3 ]" d( W; _2 r I( }! G5 o1 S
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);* Q* U$ P# S+ T$ g$ H9 s) z6 `
5 p& R1 E* |& ]6 v- o
}
# `' e9 D% J! X5 s# G: H0 Z
2 X* k1 N: s3 I4 b: p8 W else
/ m( J$ K: G8 R3 z
+ N8 k8 \& v3 }5 Y) ` {+ |7 p4 g; _( K% i: N* L
) F& z$ H3 w( `! ^3 a; { //按透明度递增的方式显示“背景”图片
7 T' C3 z/ L3 i- M7 t! p+ L! B5 h! b8 s, {/ H5 Z
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
! m- e8 v. B5 c, `9 P2 B; C+ M$ U( N: Z1 D
}
3 _. W% r: ]! T& t, ]
c- n& N6 B3 _; ? CDialog::OnTimer(nIDEvent);& C }& k- R0 d0 _( H
0 Y4 F3 e/ H1 N6 W+ F
}
% N7 B F3 u( T. j0 Z" y+ H6 v4 D5 w; k8 _6 ^: E" {) s
7 O4 v8 V) W* _! \
编译说明+ b, b8 t' [0 F+ P8 O& U
* m p* X9 e) g. q6 _6 r
0 v, I- D5 V8 h2 B" ~
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|