|
|
周鸣扬 9 Q# F' v G7 Y. l" @5 v$ ^8 O
9 p$ L. b; J6 J' M/ c: F9 ]( N3 r$ r! G
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
4 z, @& A* V! U4 d N0 i' P8 |9 Q' ]
2 K8 y2 [* i+ r8 u0 _! E 解决方案' h8 x4 h6 ?5 T- E3 }7 M) N
# f% {! _3 U$ S( R5 c
3 B- S( |7 h9 Y+ ^ 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:( l: u( K9 _6 b
& `4 v4 W1 F6 m8 X) p Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 8 l7 [! f9 G0 |" `9 }- R
* u' [& Y" V) p Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green * |9 R+ L# L. ^4 ?
, q) y0 k& ]+ {% K c Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
4 z5 g& K8 M# @8 s H' q
- k9 J& h, Z0 K2 U5 i: d# y: ] 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
7 D. n. C$ ]0 d
0 u" [" M% y7 Y' r6 _' G 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下: @5 U" L9 F! S! |0 V* W
) @" ~; d- K! j3 ~% a$ z
BOOL AlphaBlend(; O& b K' Z; W
3 d5 n0 m$ ]7 _2 k4 e* Y* Y# D' Q G HDC hdcDest,
# ]8 l7 d- C- K( ~, F" O C* X! E8 e" u, t8 s- g9 I
// 目标设备环境句柄; r' d) ?& V/ `& P: a. D
! \4 K- x5 J3 o( N
int nXOriginDest,
* c4 J$ w* |; p5 j" c+ h, A$ Q& M. [* D; z- k0 Z3 C
// 目标坐标x4 h3 y' r s5 r0 R$ `% e5 H
( `* t/ E5 X5 Y+ R, L0 n. S
int nYOriginDest, 8 i0 d* K7 V4 s" @
8 z0 r, L0 {9 T/ o' a3 s
// 目标坐标y
- H* ]& ?% i9 n, {) m' ]* e6 q$ f' G$ O4 S7 q: \
int nWidthDest,
+ f5 n7 Z0 S7 k4 I( F8 c! ]' Y: D+ a7 e8 |
// 目标宽度" g- h9 {! P2 h ~5 w( S+ ?3 |
7 g) S1 Q9 A1 v2 w! }+ F6 N' x" C
int nHeightDest, % T2 D! p+ x! m! e
. }+ U* l$ |$ v/ z) x
//目标高度+ w8 \8 u) K7 g( M
# K+ d% w2 r5 p4 w6 w HDC hdcSrc, / s. A# O5 ^5 K9 W- ~6 q; q
. w' v, D- K# |) i' S //源设备环境句柄6 l9 \& R; P0 M
$ h) [7 P% W0 ~, O3 r1 R int nXOriginSrc, 2 N/ X7 q& k% V9 q3 {3 N/ ^
# `! C# V: _. R+ O0 Z // 源坐标x
% Q9 b7 u0 S1 ^' L5 @. A5 N# F% ~% S. i3 t$ T: @. b
int nYOriginSrc, 2 Z2 T5 n2 R, C5 V
! S, ?$ p" Q/ _# X- s
// 源坐标y
/ B) v [0 Q+ V# u/ k
`- \9 Q% W% D+ F int nWidthSrc,
* b) J5 ?$ r- M. ?& L/ |% {0 A: p3 I' Q% H/ C/ `
//源宽度5 |8 F6 y, h- V5 @# |
+ a, R# q, U# J# ^. q
int nHeightSrc, 4 u( u; E8 d* k9 H. E: U. k
' j6 {4 \) {7 E8 Z/ P6 O0 L
//源高度
- A: V6 o1 T: C; Z! @6 c! }
1 O' `; `; ]+ `/ Y9 N BLENDFUNCTION blendFunction
" G. O# B- d& `# ]% T
; x$ x# y% }% m // 合成方式具体数据结构
0 ^: b0 \) a/ g6 n
: D6 E: L# _" e6 z );5 X6 C4 ]2 u+ T: @- l" K
" V6 c4 Y" F( `
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:+ D) \1 f' l" C! n- T7 m
9 q1 Z6 L; c0 L/ u# U typedef struct _BLENDFUNCTION {, H. W- E0 ]# v- \
1 c, a N' Z; o/ }* s BYTE BlendOp;4 X* v* b0 I1 q. C
6 Z1 g/ s' u o& n6 l BYTE BlendFlags;//必须为零
+ y0 r! J3 z h% S! A/ \. y8 z- e& ^$ C, g2 p4 b
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示- ]/ c: M2 o) M& m( N) n
9 c- \; K7 p, {/ o# _3 O& h
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
. X$ N* z* {$ ]& @" p$ l$ ` V' f. e( s7 {
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
, M3 {1 o' E1 ~& |( s- d, ~& G: S7 B; }/ p0 U! s6 ~
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。8 g/ C1 b* `" n O2 I
5 {9 F: Q+ I2 E( n
0 @: q9 U9 A( x3 s$ q5 B$ K: k
编程实现
$ o, w1 D4 S3 e/ O
2 k. q9 a2 u8 \
$ H! {' F9 b/ ` 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。2 x- P$ \2 D9 z- z4 q
$ v, k) H, m& V6 |% Q
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
7 `( X0 e! D9 A1 G& }0 Y' e- W
, V8 v- }7 U n, \8 }7 l class CWipeImageDlg : public CDialog1 S: U2 B2 K- |+ d
0 G- Z2 ?+ r. u n' d8 D4 ]9 E7 E {9 O j( {* H2 G
; i/ I+ g) Z7 ^ Z2 P3 w // Construction1 M* L' [: T* f& B. H
$ Y9 z- x! u, c' s public:' g0 D2 o/ Y7 h4 C
$ t2 U$ ` S) ?% [ BLENDFUNCTION m_bf;9 s$ U8 q5 }- t/ ^, N& l/ k, ^% v
, g9 M4 {9 U) Y+ a1 k7 @3 J
CBitmap cross,lantern;6 O3 S3 ?1 I" F; V1 G3 V
4 [: b' o$ |" f$ C" `! k$ _
BITMAP bmp;% m4 _( C8 H9 j' v
3 @- ~/ O0 |/ z3 ^. r6 ]
int bmpWidth,bmpHeight;
( c6 @( J G# w7 X% h! b3 ?" R& ^. z! q; h6 z& b
CDC dcForCross,dcForLantern;
9 t/ r3 ]7 H$ H5 p8 T& D1 S0 M# K' D1 g* f$ V/ W& u! h: J
CDC? dc;
) Q( f; M% D* I. m5 Y) v. _' s* D
BOOL bShowLantern;8 l+ G* X6 B6 ~: `! p
! y. ~7 E3 w6 s( Y3 N$ ~
………(系统自动生成部分)
. U) X& P3 X' h7 D( j
- Q9 L/ F; Q3 N: m0 t };
1 ?' X: g* d# B$ e9 a* W
( @3 {- D9 ?1 A N. g5 U' D+ O 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
6 B" u& u- R+ F' I( h) D d8 m8 M2 M7 L
BOOL CWipeImageDlg::OnInitDialog()
0 }9 _) e3 K, _' z6 R' F. ?5 H% l2 s8 ~* r" M
{ S, L+ ]7 X; _% p. m% r+ l
( n2 T3 j' N4 X# [/ }9 n" h, ~2 x9 o+ r ………(系统自动生成部分)! c: L1 d* B5 q
4 O* b- L6 b6 T, m7 h // TODO: Add extra initialization here
# A# q( x! A0 v9 P; p' f* _( H( {. o0 \
//初始化全局成员变量; q" g! E1 b2 I" O$ z# V) m* @4 d; H
* M5 \% q$ q% u9 \9 R: e4 z this->bShowLantern=TRUE;7 {9 S1 c0 k g3 A, A2 S x
+ L8 h6 f8 o3 D. x/ n m_bf.BlendOp = AC_SRC_OVER;3 }$ k+ \' R6 ~ m. N8 m4 d* }: I1 E
- l% Y* O% i- P+ v: I9 Y4 Z- p m_bf.BlendFlags = 0;
: `: @5 `$ I6 T8 x/ A4 f( Y5 n" Y/ K6 i% |. M6 o0 \
m_bf.SourceConstantAlpha =10;" \5 A5 b0 v" `, ~0 b
$ d% }9 q9 K$ T* c
m_bf.AlphaFormat = 0;
/ X/ j. V, D/ [' f7 c0 r: U, x; f6 ]! ~& n# f
//为节约篇幅,以下代码中略去对操作不成功的处理代码0 i- m! t* ~+ ~0 k; w% i0 M
$ F$ T. Q5 b& A! r. x# R$ W' s
if(!cross.LoadBitmap(IDB_CROSS))
" p. g2 i& A6 y. J
$ N) R- P! o# n. |, {- o. [ {
- k S& m* b* K2 |
/ w' Y' |% M+ A7 Y AfxMessageBox("装载位图出错!");
- ]) V; F1 U4 g/ `- B [
- }, f& W+ ], D& D# [2 F return FALSE;
; _' b6 X! z& E+ x, W7 o0 z
5 G+ T# G; c5 Q4 ~# R } n/ w+ W' {# b) [( ^0 j
I* S$ ^& _; w# W7 o5 r+ D
cross.GetBitmap(&&bmp);; d A3 e0 X8 p$ M+ Y1 d
: ?1 H* K1 H5 @ J) N
lantern.LoadBitmap(IDB_LANTERN);3 _! a9 k0 d1 x) d0 M z
( @' b% {3 k5 ~* {: k cross.GetBitmap(&&bmp);
3 c+ a6 m+ j( A4 U1 }
; Z/ D7 l5 w+ ] //获得位图的大小信息- }% W6 m, ]( K- _2 b% @' W& Y
6 i7 f, V; G! _4 {: `* u; ^& F
bmpWidth=bmp.bmWidth;
" l) t, e) u" h7 R" { s7 U
0 C; T' t. h/ a- C# W5 d) b6 V2 [1 I bmpHeight=bmp.bmHeight;
0 L0 C! u: k [# B8 @% \6 N* k8 H* T9 Y! J5 |2 P1 D
dc=this->GetDC();
# H8 ~/ x. ^: F0 d" _4 R/ z( _' R7 X+ b% {
dcForCross.CreateCompatibleDC(dc);3 |" S; R" h5 ?; L2 J
) ]" x& v! i" s. |, Y1 u
dcForLantern.CreateCompatibleDC(dc);7 l! \% i; m1 g! p; Q& r5 O
0 }7 S! v$ l; ?3 X' b8 O7 S& w
//将位图装入设备环境句柄- a1 C7 R1 C2 c& \/ C5 x
% f" B1 [( [" I9 p, D
dcForCross.SelectObject(&&cross);
. N' [' s# Q& g" T, H- K) |/ E9 O1 g+ N1 N
dcForLantern.SelectObject(&&lantern);/ [, L9 X5 j" _) C: G! _* r# u
4 a1 T2 I2 c7 k$ S/ B- ]1 R //打开计时器( Y& `. v5 k- l# L [3 ~0 c
6 g1 A# B% l- c( |3 k4 L" P SetTimer(1000,50,NULL);* {' Z, I# O% r z a
; E {/ U- d3 {- \( E' I+ c return TRUE;
" C C) P) i+ J* H5 {# |( Y2 Y# }, E A" L# i+ S
}: [( Z: E0 j+ I6 ~ F1 N/ S- y5 G
* v1 `' g( E' Z8 @) O void CWipeImageDlg::OnTimer(UINT nIDEvent)' z1 O4 ]3 L$ h' p2 H! b
! X( O& ]) F. |& a' F' y {: @* z) j. t$ M' ]& H: l i
/ t0 _; }: s# G- y0 v //图片透明度每次递增5点
& T" z# J& ~, F
9 s* e6 V+ J- ?( w, D# E m_bf.SourceConstantAlpha+=5;
- p7 y1 u0 {5 X9 K
! u" Z3 ?* P+ p) d //当第一幅图片完全可见之后,显示另一张图片/ ~3 b6 Y0 z) E+ c2 l8 {0 P* X- ^
& w" q/ l- F* h$ g4 F; M9 Y" Q if(m_bf.SourceConstantAlpha>=200)& r9 k' {$ e: H
5 J, w' F1 B6 p+ k- S2 H
{) Q. K0 J! E2 s0 }, R8 n% C7 Y
! ~6 n8 ^. d; v7 {
m_bf.SourceConstantAlpha=10;4 k D$ j+ K. S
9 X- s4 m3 b1 ? M: F
//将bShowLantern做为显示标志,确认应该显示哪一张图片 # X' ?/ W8 N5 K! C: E: D$ a. ?+ f2 E8 p
5 r G$ s2 l( G. C# }: v
bShowLantern=!bShowLantern;: e* r6 z/ z0 ~' u5 m) ]. {$ J
9 r4 \. H# s3 k+ @% x% I; h; Q }- r0 I \; J, X M
0 g+ Q& c5 l. A if(bShowLantern)
, _ c# z' [: j5 A" `8 r
t4 T% X( y" M& U; l. @ p* \ {8 S4 w6 h$ o6 S$ q' \; \0 A1 m
& ^, h1 S# I6 ]. F% Y/ n! O- l) U
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);, Z/ G) K1 ?5 Q: Y/ y9 i. p3 a* I
; D# P: l) e9 ^4 p+ D3 ^. w
}
2 u) T- M5 |, B+ r" q! p% l2 \
, q% t/ P6 p( u# y I" ~9 A else9 |! R W2 [! m! [
% i& b5 o! U2 o6 K) J; u0 ~ {/ \9 u# L. ^& N- k
2 y2 R4 ]2 \, l/ }5 Y4 h //按透明度递增的方式显示“背景”图片
1 p$ ^. f' k1 J0 b+ E: ?3 c
4 F* N2 o7 O- B& W% P, { AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);: g. w* C6 A, c1 J# M$ r. ~- {* k
7 k& Z! m+ G4 R/ `
}
! f E; X: n- [7 J- H/ ~1 n( n u3 B# x! i1 z" s/ F% R3 H0 ]
CDialog::OnTimer(nIDEvent);1 j& E- y0 B6 I# L# I- Y
; p b9 g1 {. ]0 J, ~ }
% s2 E6 w0 r* Q _4 T: d; R1 J
7 x- m* T6 e; [0 M: W0 t5 O* z- U" _ I: N
编译说明% t. Q3 A9 E, T- A
9 J4 |0 R" @1 r; ^* i+ r
3 N1 Z1 j6 @ O+ i* ? 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|