|
|
周鸣扬
5 Z9 V% z# \8 F1 Y) O2 _% s; R: L- b2 h
. U Y. ]% P* x 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
( y4 d. y9 c( c6 {( n
( S& `0 s' R* `% e5 Z3 O) O' L2 J' c2 |, T' l
解决方案
% B W% k0 G4 S8 G- i( X9 \5 E% H( o4 O! V$ _: `8 l2 @
2 F+ K z, [, P) d( k9 p5 C, @ p' o 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
/ y* f+ [9 q5 K* X% o4 p
. f" O5 `0 h9 I9 q. U Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 5 F9 X! Q9 F8 ~; w+ P( V3 j6 l" _" |
6 p5 u$ {) |1 A' ^+ P0 H" ]7 b Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
S; N! {3 E( q% c" B6 n/ e! L' A+ K! p# r
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue) V2 A" J7 D+ B# j8 {. f
) }2 x/ b0 \8 u+ m# z' J: o 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
9 g0 Z( _: r( R8 `; u
4 s9 T* B8 O1 i, q4 l& E 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:0 }4 q+ {+ I+ U8 X2 b- C& N
: e8 E1 Q4 Q# X* O8 ? BOOL AlphaBlend(
) K! v$ d7 J2 T5 k; q e0 Q* D% X! h
HDC hdcDest,
; t; B( B3 S6 j E5 g y1 d/ E
0 C2 r Q' l" U9 N5 A+ h+ { // 目标设备环境句柄
: u1 k' x: d& T( f9 a1 ^* ?# p
) ?& j$ y" t" X9 l: k3 }0 \1 X; ^ int nXOriginDest,
- G# S6 Q9 [1 S* i5 @" G: s m4 {
// 目标坐标x# M: ^6 a8 D3 n
. U. C+ M* ]: W* {9 ]7 @- A int nYOriginDest, # K8 V; C4 V; H) {
5 p3 S4 x2 c2 r& m
// 目标坐标y' ^. }6 Q+ \$ Z+ A
6 Y( y: e% n. M3 r) O
int nWidthDest,
K" q3 F h2 {2 E, n9 s7 d. d
// 目标宽度0 C( e1 V: x- @9 P3 B- r
# x1 @# }% p. o9 \
int nHeightDest,
0 u# ^" [% l6 K. H8 ]- Q9 w+ Y$ v- u1 [0 v
//目标高度 C0 `- o+ A* g8 l0 W2 }
( V& g5 x, w/ D( N HDC hdcSrc,
4 Q9 j9 |$ M, Q- r: @/ @7 z% v' d9 r7 b5 r3 T7 B4 W
//源设备环境句柄
$ b | ?* N; B' x6 T7 h; d# f$ j
& G, Y. W- b$ {# M int nXOriginSrc,
/ y/ r8 u; O" M5 B7 G8 l
' C% B5 i8 q6 u( ~+ I6 a3 Y9 @ // 源坐标x
4 H( V$ k: N8 p) }: Z! r) `% `+ P" I# b9 `% y
int nYOriginSrc, & b' t- {# ?2 Y- {6 O( N
. |2 U n2 [0 X# _0 e! z# m // 源坐标y
% l! W7 F$ G8 d7 w+ V( S* {! {1 z* I7 K7 R6 O9 N
int nWidthSrc,
% I7 ?7 R0 D* s1 i6 i t5 m `$ O% \8 _
//源宽度" [" ]$ O* f; q- `! U6 v! W
; I4 |5 h9 M7 B3 ]
int nHeightSrc, 1 `" H! X o7 }+ ?1 \8 L, [
6 R; A2 u2 m, W0 [, y6 f1 v5 K8 F' t
//源高度
$ d' B; S+ ~3 x; G
, b6 g0 G! p3 D: y* V7 D1 h. O- { BLENDFUNCTION blendFunction ( U) j% b# {& K2 d4 r* _ V
. p4 L0 G) Z; M7 K
// 合成方式具体数据结构
0 }7 Y4 z5 k* {+ o# N* [4 N4 e" d: W& r2 C
);1 |( T% ~, x5 }
' q, w1 g' v5 h BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:/ V' w$ U+ T- u$ d4 m! t; n
3 ?9 g9 r% K6 ^% J' x0 g( S' r! i typedef struct _BLENDFUNCTION {
1 K1 y8 O, x% J/ n! |+ P5 t; |$ h/ ?9 w
BYTE BlendOp;$ i' X9 w& |9 Y
5 K6 m1 P: f) z5 \- E8 P BYTE BlendFlags;//必须为零
4 V3 v& A, V, K8 c9 b* \* w3 f
/ U8 A2 F" t" a2 U: M* {0 \& } BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示! I" Q" y- y4 `
. j7 l. T; C% T s
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
5 T/ i0 q, w" E/ w7 Q$ ]$ z/ [ Y3 }- K
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
# X, }% ]* w6 r8 t; A- B1 @0 b l J9 J# \
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。3 x$ q9 B: S& [- E* M- U
& O% {- \1 j, z" G( s' X
/ Q2 a3 G& }0 q* X, D
编程实现$ t2 a1 n7 Y$ W7 t$ W$ O% ^& q
$ F4 r! o5 l1 H/ X& ]& }6 ]
3 u5 j9 W) T7 K+ p- t5 l 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。 d6 e" A* v9 S" {
4 s( F; Y% @7 i* ^0 |1 r 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
6 L; ~. l2 L; {7 X1 \' I
. m* H* X* N; g( l& b5 [7 R9 O class CWipeImageDlg : public CDialog3 x/ X6 {% `& a! l+ r9 {, d& v
9 k2 [+ m' O$ B, v$ w! k. E {$ \! d5 h3 g8 \1 n( E$ J
: S2 F# t. p I2 p. s // Construction- C- Y" v) ^( y- e, j2 z$ t' ^5 u
0 Q5 w5 C) J! a
public:' V; s- ^7 {3 r& S6 `) F
. h+ `4 c7 I! X9 s4 a) p" {
BLENDFUNCTION m_bf;4 r% P/ v/ {) S3 y
5 Z7 z2 `3 @; G$ ~" K Z# N* D CBitmap cross,lantern;
. o. q0 ^# h( M. w6 _# T: A K; E7 X6 d' K* n
BITMAP bmp;3 i+ ` h5 U, B
5 p" y& ]: J, N. Q
int bmpWidth,bmpHeight;
' I+ T) N# g% v+ X' ]5 S/ ^, Y/ q9 `) l. f/ Y7 X, l! h+ Y9 \
CDC dcForCross,dcForLantern;: t0 C, K: r7 K4 _8 s0 L5 a
. E0 }0 A* z4 M% Q- t CDC? dc;( c, I5 s( U+ h7 b* p
1 C$ S/ I+ b2 M, V
BOOL bShowLantern;
! q, A! V2 {5 I8 L1 i% x, {& q3 @
; s7 M+ e" G c. o" W" I0 m ………(系统自动生成部分)
# O5 }+ |3 ^% ~, A1 M+ i8 n2 V2 g" s$ N9 i- L9 Y" B
}; . d+ e! j/ q7 i( d
3 H2 f" m, ~+ N7 s+ R! w 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:6 c6 M. x5 h* f a; i
" o+ r- y) A) Z6 ?( T; \7 A BOOL CWipeImageDlg::OnInitDialog()7 B7 o4 p) ?1 I. i- g' x2 J3 ]
- `/ }( f: h- D8 D i$ o {
. _7 {9 Z8 H* v: y0 [8 p/ i2 d8 {# A/ m4 j* |: E" b' i
………(系统自动生成部分)
) [3 s# J) S% F: T, n0 ~7 g3 n6 Y Y" \. Q
// TODO: Add extra initialization here& W. z+ O& R6 \2 U/ t
" F& Z: I+ o6 S! G1 T, x
//初始化全局成员变量* J0 r7 N& ^( z2 T) w
0 n5 e( }* Q: y* _$ h7 l
this->bShowLantern=TRUE;8 a1 m6 Q( Q7 \& {8 u2 K. g
/ n1 q) X h4 l m2 E6 r" Q. A% G- M m_bf.BlendOp = AC_SRC_OVER;
4 I T2 c2 N: u4 C" y* E* D& c$ s1 p* B1 Z" G0 k- n
m_bf.BlendFlags = 0;
3 n5 j- h# ~& T% f. V
1 g. P! A5 y/ V3 I% V6 J m_bf.SourceConstantAlpha =10;# d$ P u9 f8 G2 e7 N% X0 l' Z4 w
' o }5 W: `/ @8 E; h m_bf.AlphaFormat = 0;
. p* g6 N- ` X ~5 Z* x0 j+ m* j/ M P4 f: ]
//为节约篇幅,以下代码中略去对操作不成功的处理代码0 g/ W) c" H" g4 v! I6 ?
$ ?& o& ?! M& C) k. L
if(!cross.LoadBitmap(IDB_CROSS)): D: E! n% g3 @/ }9 O
; k. h w/ ~1 m( s' K
{
k; d! i: E l9 E" C$ T
; g* h: y' d# p5 U, T! Q AfxMessageBox("装载位图出错!");
& s; n, N- b8 o8 F0 J6 j; \7 R2 ^, r* |9 ~+ s# z% N, h& K) u0 M
return FALSE;
" F* U4 F9 i9 U q
. v& @5 z M" E% i; c$ W }
0 y: @- ~0 b: _; q% }" l% |( d2 X# t) {: e7 t/ z! j+ @; W; n+ d
cross.GetBitmap(&&bmp);2 b r) u2 l! a+ S
; W, }' J" ?( M
lantern.LoadBitmap(IDB_LANTERN);
) `! J3 E4 b# W. k; o5 V
9 N! n6 e$ N4 u cross.GetBitmap(&&bmp);
& I: E% H% L( Q8 c# X6 ~% O
0 c1 ^! f4 A) \ //获得位图的大小信息
: e: c4 \' P# O7 K& G/ ~* b& \7 I/ b3 S) b1 [, R0 Y
bmpWidth=bmp.bmWidth;
( B# l7 e% A3 x% x& a3 W8 X6 i5 m& T
& ^4 u" Q: Y9 ?; { bmpHeight=bmp.bmHeight;: W ^9 Q- h6 \5 f3 F. ]
; A& G2 Y5 \$ I: Y1 X dc=this->GetDC();
! `9 l4 [$ f/ \" C8 E3 }% R. c* D, T* E& [# {: t0 `8 n$ m$ [
dcForCross.CreateCompatibleDC(dc);
3 f2 V7 J( s1 n# ]( _5 M* `; n* b: d! L3 N Z
dcForLantern.CreateCompatibleDC(dc);5 y. M$ ]! M& q1 Y( C9 p4 A$ }! x
, A% H4 p% O1 d8 e4 P [1 e //将位图装入设备环境句柄
0 A, k( n) o; C7 @
" s) `( Q/ l4 L, p dcForCross.SelectObject(&&cross);! X# n7 @( p) F2 I3 }, R) d! A
1 m; J3 W% a! d- Z+ r2 m0 v8 [
dcForLantern.SelectObject(&&lantern);: ^1 f; T( h! \& V
M: G% z; e1 V2 Q! r6 p/ v
//打开计时器* x' { o1 D }( `
1 D5 j: l5 c: N& i2 o2 n. T: F
SetTimer(1000,50,NULL);
2 o9 d, }; J4 @% B" N6 `* K; L5 ^( Y) M
return TRUE; 3 C0 Z2 h1 F& A+ ^
2 p4 g' E* {7 h2 \, y3 S } p7 q( C' K" m7 `" Q; t, _
B$ k/ v7 w, \3 j
void CWipeImageDlg::OnTimer(UINT nIDEvent)
, |+ a+ U( [0 W; P: ?
/ l$ m: `5 x: A" T" d" x9 }% ? {
% U& W& p5 b a# L i1 Y3 s" X( n6 f( w
//图片透明度每次递增5点
) G* m; T) I7 B% N% S3 m- \. b3 w, ~1 m! k
m_bf.SourceConstantAlpha+=5;
5 R# M; r. _3 n5 T/ e0 e9 B- ^* d$ E9 M/ T [
//当第一幅图片完全可见之后,显示另一张图片+ j9 A, T) ]1 L5 a4 ?
' o1 P3 T" I) D A) ~6 o/ {( P8 N if(m_bf.SourceConstantAlpha>=200)1 U2 C `+ }, H+ C/ W0 j
" f, P+ E( R! C" }# N, \ ~: G6 e {0 r/ E. C5 x/ f' a4 h
5 G) _+ H' @! [0 c
m_bf.SourceConstantAlpha=10;
6 h. K% Z) ~3 S. g/ i2 M2 T# R6 O, k7 f, G: U& R
//将bShowLantern做为显示标志,确认应该显示哪一张图片
2 l% d' J7 Y- Y O5 ] ?: t+ C& g. t! P/ q3 F
bShowLantern=!bShowLantern;
9 ~' J/ T: F4 n1 j& k7 A" D$ Y. V5 W- E5 v5 C
}
' V: W3 }( O n1 @4 Z5 I$ L) C. l
: ~9 W3 M* e9 J, r7 T% U7 T if(bShowLantern)
0 S+ ]; p7 d3 x
9 z1 ]8 H3 T q4 o1 P m: m3 U {
4 p5 [9 A2 u3 {% K3 T; W
7 Y" u' E; D) B% ? n7 T //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);# E& m3 V9 D6 W: ~; l
9 E0 o) {9 }+ l# D" e; c& \3 y
}9 X- P R7 {5 G3 \% g& k
6 I3 G, t0 u* y0 m: n! w0 Z" z
else; k% O5 ~% r$ z/ U4 q; {
. B3 {1 _# m% z1 n5 Y
{, T$ {: k0 W3 U7 ~- y: W+ h3 u
1 d" N2 t9 d3 P. Z
//按透明度递增的方式显示“背景”图片
" _% M/ u+ e" D. f2 s" P, z0 O6 G L# L+ D. ?3 g3 S4 ]3 N
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);7 b2 A2 Q7 l! i
) B: [" \- I) A& j }) K1 e3 ?" R. L. `- f2 _' z4 ]$ Z. ^; B
8 C$ _- j5 S C2 ?5 F! F/ g
CDialog::OnTimer(nIDEvent);/ {! s+ M& D; ~1 U0 X9 |
5 T1 n/ P$ U2 e* j" V# h6 } }
2 K5 K* B) x# h; P( ~ [$ Y& P6 q2 |, ~# F- ]
8 C" a7 C. U: U8 S& k0 ^# \- M 编译说明1 k, j! K+ l5 J+ |# @% s& p! t4 p
p2 }% O% N7 R) Y& i" g! _( n2 A3 e& Q
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|