|
|
周鸣扬
: G4 B6 L( z- d) g% |/ k
' v( z& G) ^9 P1 v 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?$ s; v7 Q6 V9 Y4 }; l! Z1 a
# w$ K" \3 z# k! t
9 j+ f" E# S Z4 ^
解决方案
1 t: \' `% L# }5 q: z) g' l# Y4 v( ?4 Y
9 H5 ?! v/ [ \9 P+ D3 D
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
a3 O: _" R3 u ?
* Z- E. H. k4 A* \# B/ I Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
5 y' d4 f* E3 \- q" O5 S- V% Y* W( V& a) J
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ( w3 Y4 w% L1 g3 ]; | U1 v( P
; e6 m3 }1 x. u j6 u Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
/ B! j% c* R" c' e. W
( L8 H F1 G7 Y1 ]8 ~ 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。3 X% _) P4 n8 s6 ~2 O6 n! S
9 \) p. M4 }( U1 l: C% }
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:/ I& T. Q1 n) I5 X/ T
7 N& p; n/ V" K2 L0 L. k4 g BOOL AlphaBlend(. `# D% m+ K6 q* @$ B/ \
2 P9 a# h+ m, w" c& I* b# V, S6 ` HDC hdcDest, ; c' [2 a) A2 ~+ Y ]
6 t0 q! T8 i$ W8 K {1 M% w+ H
// 目标设备环境句柄
- W3 H" U$ v5 C( u5 s' a w2 |. j; j9 b& Z$ Y5 v% r/ a m
int nXOriginDest, 3 Q, j* h v# f) `" u( }. o
& n2 B# t6 @; G6 g% p- Z4 p
// 目标坐标x
# x P* M! ]% u/ \! x- j }' u P e& K, _
int nYOriginDest,
% B3 j( x) n# L5 ^
5 q5 C2 @- b1 X! D$ v // 目标坐标y. G. H% E/ h9 x, e. V9 \, S
' A1 ~* \4 _ x" ^$ O
int nWidthDest,
3 `: ^4 G0 Z, [* ~* E8 a. {8 @0 d, G8 f) P
// 目标宽度* }+ ^ }) v' N! V9 g* a
6 X" w6 {( c/ f% b9 C
int nHeightDest, 2 I" r, \& x. t1 L
# Q: C. h1 ]1 m8 U. J# m
//目标高度
# u3 f- p. K, O( n, j3 |0 V% i0 k0 A/ P
HDC hdcSrc,
; d# E% d1 l d6 r6 f! J) y z3 \- l8 {% m
//源设备环境句柄
5 C: u$ O' a' N+ P' T
: y; P; [' z/ i int nXOriginSrc,
& L/ Q6 N |+ X. W! I& n5 N
2 N7 k* a" R" w5 |: Y // 源坐标x
6 R) _5 s. M6 C0 y1 `% C) }
; {, ^) Y; y( { int nYOriginSrc,
# S' r8 O1 f. `1 }
. ^6 w0 G: @2 a9 B" ?: o2 @ // 源坐标y
6 W# S9 P; @- a) Y% `% d# n$ t/ i% x& R, Y) N
int nWidthSrc, 7 L: [8 e, Z4 |$ ]! ]$ H
# F2 D) y% ~, Y8 F8 H
//源宽度
( }' T2 a( N% D7 h
5 r: B' s: N Y. x int nHeightSrc,
4 _+ Z$ Q% U `$ M
- W0 _: M7 i; _4 n* K$ z //源高度5 v1 K; B/ W+ r* c, @
h0 K$ h5 X& t/ p2 F1 o
BLENDFUNCTION blendFunction # e0 f/ ]: c3 y( Q
. c# q; s: _1 H; z
// 合成方式具体数据结构- L/ I8 ]0 M* n5 P
% A9 C+ H2 T. i, \3 Z& l );
- c( D1 w! s: H& e2 Q) J& U# k) k5 v, o- H8 N
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
+ f" U2 Q) [& S6 y+ o, T
" i P2 G A5 ~2 I$ z typedef struct _BLENDFUNCTION {; _0 Z/ c- A2 r) ?/ c
6 i+ R* ~# P9 v& j; F
BYTE BlendOp;
: Q i# h0 }2 l" C" B$ |; E
" y; a l& ~5 @- y7 {) Y+ j: e BYTE BlendFlags;//必须为零0 r2 i2 a' }. R$ M Y
# z2 N" @7 |: b/ G& n8 y3 w
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
/ E6 f# }+ l5 v# J3 @/ f
9 A1 e! C& C$ d6 ~+ | BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
& |/ \) L$ f" t; ~/ ^& R. i/ R. J# {
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;& R5 F( X( [2 {7 v3 t; x& Z+ ?
( U+ }8 ?1 j7 s9 p- ^+ W6 _+ a
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。6 j0 D- Y0 e* K- ^" h; m$ k4 J# Y
" V& h0 |3 P/ N K5 @2 k( L% b
$ k3 U6 u0 t. N' n5 M ] 编程实现5 j) g3 i' c8 X/ K+ W, p1 A: C
4 D O2 q: W1 e4 j$ i0 K6 s
+ D, ?# n& y* w1 K* i 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
, }* W8 I9 c! b7 J6 J% Q5 i+ k& m B; I# v. _% K5 Y
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:4 d, g5 ~' R) o/ q8 l' S: {( S6 N, b
8 i: C3 i/ l0 U
class CWipeImageDlg : public CDialog
' b' A* t) V. Z0 ~3 y
6 e2 j! c) t9 E/ _' {+ t9 l {
+ o+ q+ h' g- E4 ?+ m! u* m
; d- B% u# V, `' h6 v8 B B // Construction+ ], |) _% z8 @& m
/ b3 y) O% O! u* R# ^ public:/ [, Z+ ~; ]+ Y" y+ R; s
4 A- u0 a$ {# I- f3 i) F
BLENDFUNCTION m_bf;; c; L3 l8 i0 g: P z5 e
5 C4 j5 s8 U) b( v8 ` CBitmap cross,lantern; o# Q) k9 l5 S/ d) P5 J# ^3 F3 t
/ b/ A S0 J$ m+ E BITMAP bmp;* X8 A( Z( H; E6 I3 {% R& h* R
8 {# Q! i; _- f( m. L int bmpWidth,bmpHeight;
+ w4 v R" Z! ~3 V- b, w0 {# n
% q/ J/ i8 D5 g( p CDC dcForCross,dcForLantern;* `6 p9 X! h- f% R1 P! y1 y
0 g* k9 J$ y0 C2 R# ` CDC? dc;
6 l9 n0 L8 h$ K3 l6 o) l
+ `) E7 u/ L o3 g8 C0 } BOOL bShowLantern;
- e/ Z! h0 G4 c4 @. I. S6 t) E$ v' d3 X6 \# B
………(系统自动生成部分)
# G0 `/ V8 h! c4 L M
' w$ [4 K! Z7 B$ ^- F/ E* i };
8 ~/ k R4 x- q1 w8 y9 |4 K5 m
- V; |; ]9 y, L! K# X6 e) E/ Z 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:) [9 k6 H O( a# U3 e
0 Y( F! N! U2 P6 r. b2 M ?
BOOL CWipeImageDlg::OnInitDialog()) N. L, V S+ `# v; \% w
4 f3 H8 t# n& Q- i6 @# J) a
{
3 m3 C0 {6 Q, i) }' e
% b2 [* _9 |1 E7 D* q ………(系统自动生成部分), a4 c+ c' w: T5 P: j9 w: s' D: D
3 P% u8 e2 L0 N! m // TODO: Add extra initialization here
4 Q5 q u/ T; x. v$ {+ S$ d& T/ o6 |- e9 X8 t9 n5 F( p
//初始化全局成员变量& | e5 S. [5 {8 N8 B
$ {! q3 S* r+ i5 n. N this->bShowLantern=TRUE;1 o2 w4 M/ a% ^* z, n! U" k
1 C5 ?/ N5 F3 Q& }2 b$ p& R
m_bf.BlendOp = AC_SRC_OVER;1 T3 P1 s2 D/ O3 P
& @6 @: i* H- N6 z0 x+ R
m_bf.BlendFlags = 0;
" q; I( z! |) g( x: ]# X9 D' D( S- u2 L0 k
m_bf.SourceConstantAlpha =10;; C# g% ]4 J' C8 ^
{$ p- _- c! o2 S+ I6 t% y: j m_bf.AlphaFormat = 0;% L. q1 r: {1 D; p% T3 u2 R
/ j2 l2 D/ P+ C: Q% T //为节约篇幅,以下代码中略去对操作不成功的处理代码, L( a# F, w- ?9 i& E
5 R m- y6 M, B- p9 n9 Y if(!cross.LoadBitmap(IDB_CROSS)): J- j3 J4 O0 S0 S
% W; m6 _. ]- g
{
+ ?0 p- y6 {1 Q P/ L6 ^
$ x, w. X1 s* E AfxMessageBox("装载位图出错!");0 s, _" y6 }- L& x5 \9 R9 m
8 t! e0 D' T% l A' Q. _* C
return FALSE;
5 h7 l5 v" F% C9 D0 [$ v9 z& M- Z' B; T! {, i/ C( O7 x$ I/ a" ]
}
! A. d) u/ v7 q) s1 ~ I: `' Q; V" X& y$ a
cross.GetBitmap(&&bmp);% s/ b2 R- ^# m# t# H1 `
+ A9 o( b+ b. G8 H# w lantern.LoadBitmap(IDB_LANTERN);
5 Q5 |# U! J, u
1 e+ ?; d1 L: y* U cross.GetBitmap(&&bmp);4 u6 _9 e) m$ l+ v3 t
* T& \3 E) c& N) e7 e2 G
//获得位图的大小信息
3 `! l* Y0 E* H7 d) a2 Y+ c" S1 }! } u) m! I- x9 T `) z/ R
bmpWidth=bmp.bmWidth;
. J$ b0 a; K& w. N3 ^: m% J" u9 ~7 c) `9 z
bmpHeight=bmp.bmHeight;
: Y, D8 O+ S8 Q: A8 D9 A+ m! Y) r/ L) R( d j! U% g
dc=this->GetDC();% E7 U$ G5 k/ q0 V: T( O4 q
- R* B) p- J+ G- Q' g+ H+ E4 h dcForCross.CreateCompatibleDC(dc);& ~% |! \- r$ F# D3 t- c
- d3 j6 M6 H. n# _, Q8 s
dcForLantern.CreateCompatibleDC(dc);
) m; H! _. o/ K& V0 W$ O% J
& |7 B( E( i+ ~ ` //将位图装入设备环境句柄. n. Y1 r: y6 W) e, ~
/ K0 d' j5 N4 b$ N5 A dcForCross.SelectObject(&&cross);
8 {* j- F* a- p# ?* ~+ y
5 O' |' T' ^ ?$ B0 E ` dcForLantern.SelectObject(&&lantern);
* H6 q! A+ j7 } D* y% }& B ]- Q/ y: E9 h" V( P" a
//打开计时器
' L6 {! l7 G- B# M: d6 ]* W$ G
. F$ P. q& l; _+ m8 {! B SetTimer(1000,50,NULL);5 F# F4 S W$ f. w$ R' U
$ G% O' N1 ]0 A8 F* D" j
return TRUE;
' R( N# m7 n: G6 N& ^8 R7 f& I1 x$ L( B" Q) ^3 p. o
}
y+ b1 S% W3 q( ]- F9 g! v
, }' C' d, e% c' _/ \ void CWipeImageDlg::OnTimer(UINT nIDEvent)/ `/ d. U8 R$ ^. L T
6 {+ c" c5 k. p% ^
{
/ R" A% z, ]) O3 p* V( r) f, B- K4 \
//图片透明度每次递增5点5 B% b3 B7 L# r( V0 I
# F! g7 Q8 I( X' B: G. U! D+ u
m_bf.SourceConstantAlpha+=5;1 h: V) U2 x* | |- x" o& @
( q S, D5 _! ?- l //当第一幅图片完全可见之后,显示另一张图片
$ @) v7 I, j+ ~0 i5 C6 F- h0 r3 Z7 N( b8 E9 Q2 q4 s
if(m_bf.SourceConstantAlpha>=200); o' X1 t( n/ k: d' r/ \3 V3 h2 r+ q
, [6 f$ A" d" R {
5 U9 H N K9 o2 |6 N( q
4 I. ~3 ]" t+ l( z' C, ~$ L& T m_bf.SourceConstantAlpha=10;
* Z( N4 r2 g, b
( b6 [. W K; p! w //将bShowLantern做为显示标志,确认应该显示哪一张图片
% N" {: {. ~6 _9 j) _1 \" S2 [4 l+ H9 b
bShowLantern=!bShowLantern;
# ~9 U8 ~ e- [: i3 {
; G4 D. J4 Y5 s% C6 k+ O6 ? }$ S. B8 {& ~/ C, @( K, {8 ^
. |: @2 O4 K. l I
if(bShowLantern)
! E2 l3 y" M8 c+ ^2 ]* o5 _5 i, q+ r6 S# v6 ]9 `9 x) G
{
- k7 r1 {. k; n2 r) s* [: O# D+ S A5 A1 n0 e, |1 H
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
8 D1 b" y; I) e3 [. x! v- K' `% ]. G, t) B( O% e! J. h
}
% Y% ?7 U+ L( v( ~2 i4 Z' K+ y: B* j6 M) q
else
: ?5 z. \( H0 z; @% P; L+ C3 [$ Y3 d& q% p3 X8 f3 _& n. S# ~4 D2 R0 Z( ]
{
: I/ F* Y8 d" }
( ^, Q6 ` ]7 F% ]( D% t //按透明度递增的方式显示“背景”图片9 |* r. f# b; t1 Y7 n% I
; B) V8 _. u" t) a AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
9 U( Q, {! j7 n8 N8 f( T; v3 m4 A+ t# b/ i5 w% @# n
}
: H) ]* M4 N3 t- \ s2 A- {) P2 `, f: H' B/ H
CDialog::OnTimer(nIDEvent);4 m' x' t' [. b3 F3 X# H; t6 F% E* Y
; v8 G$ p/ j; q5 F }
6 Y. G2 w9 a/ @6 M, O |0 H& v/ Z( I: w! U0 n
" k7 E7 L) [# T- i, ^ 编译说明
0 V: q" n9 u2 c, y. v8 M. ?3 e0 \2 u. c
! C& f5 p6 k. J6 j3 X S7 E 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|