|
|
周鸣扬 . i; R4 l& W& i, o+ k' c
" ~' e; h6 L' k' \- m! d, @
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
+ |6 P. o y1 F' X3 x8 ?* c( n, D- J, }( n# d+ Y
% ~6 K, r9 I1 V8 n8 X 解决方案
( C+ q7 k+ o8 F/ J6 U# a: D1 F4 n8 J9 l4 B4 O. T& Q0 i: P
% N6 J, r. h# b, G& [ 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:! l( M ~, I6 w* b5 s1 a; y
$ v" l! e* g- G2 W Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red . E! m! U/ K& y9 S6 Z
) p8 Q' W7 D, W4 v) U, V* s, z
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
% Y- J; h- b6 t" ]( S8 o4 f* a2 n/ b$ N) w6 ?1 Q2 e
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
4 H# U5 s& \- T I# m/ X6 i" V) A* J6 A% V8 b. [) r
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。$ d7 L/ x/ S, m) A. B; p9 `: \/ H
' J4 j4 p- `9 c- ^ e x$ s
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
6 B$ r* C% @% e* I8 o# K* s8 z7 h C4 ?
BOOL AlphaBlend(5 {; p' f0 @& z, e# _
) n' U" _' L- q9 e- d
HDC hdcDest,
k* T9 ?1 R( N# a3 I; {& L
4 l5 ~9 r" G' h8 j3 }# C# k# i // 目标设备环境句柄
; K+ o$ L3 Q' \. B4 ?; D; a, b; _! ~) e# ^+ e7 P1 T a
int nXOriginDest, 7 b( g; Z) T4 q* \* S6 u2 Y) S6 u
" Q. H9 g3 b% }+ m // 目标坐标x
8 \0 B) ?& t0 Y: Y, [+ D3 K9 T* x* F$ a. V1 J, ]: P
int nYOriginDest, 8 _* i0 {3 t6 J* k- L: t4 `" }. l
- J# n% P* S" Y
// 目标坐标y) w# O5 [8 P4 T3 X( m* P% k
1 @7 [$ A" Z9 _& O' l
int nWidthDest,
, f/ m9 d- O! u: j
/ a+ U3 P t* ?, G: ]3 z' z6 N* Y( R // 目标宽度4 V% r: G3 D+ x/ E2 w6 R* o, O
- I/ N# s: y* ~/ E int nHeightDest, / ~1 T; Z; g: l \
5 U1 l3 B# H# F- ^% Q: M/ D //目标高度
: |4 h8 F6 _( R6 E( z) f. _1 c; I% ~4 Z
HDC hdcSrc, 9 q, X# s# W. R" k/ N
: T1 H& n. Z% o0 x0 h) @ //源设备环境句柄& w( U; a { I# }! @% S1 t
, [8 r% E$ J3 N! F- M* Z8 E int nXOriginSrc,
( f+ O* }& e; H$ c6 S7 N: D9 z5 k& u
& W% m8 d2 W, V& p5 f3 O9 H // 源坐标x, O/ J0 d$ Q% h+ Z9 j `( t
0 T. w0 C; ]: \! v4 k0 M- J
int nYOriginSrc, ' c8 [$ F1 X7 ]6 e: c1 p) i
" K5 \5 p* e" K& O: h( S // 源坐标y
" c& k) e3 G5 O9 x& V7 y6 m6 F) k+ A. b5 i
int nWidthSrc, 4 J: L7 h$ F# X8 j% S2 e
+ m: R! l6 N) @* N% Q. }# i/ D$ ^
//源宽度3 ^+ P* \3 K& F% E$ V! R9 Q
4 C3 W1 a0 n O/ h- y9 ]1 X* T& ]1 q
int nHeightSrc,
7 c- D6 U1 {4 Y$ d: M3 O9 H2 N) e$ a( c7 C |# f
//源高度
+ }/ t7 E6 i' M1 t9 b9 u; t) E1 \. b: n" q* I3 u
BLENDFUNCTION blendFunction : ?4 g! S+ |' N$ b; A1 L
! g1 A8 X, t/ `' @$ m& E' w2 q* ]
// 合成方式具体数据结构6 N. C) m9 G* ~$ L& B
. r1 h/ {' z3 v' q* ^+ q
);, t- W7 M& i* ~; r9 o
% V4 c8 V! @. v E) u
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:) n3 E% E# Z: ^7 ~8 F
% S+ j$ a8 F7 o- P
typedef struct _BLENDFUNCTION {- z6 p5 E& I- l+ b7 ?. f9 k
. z( `6 y! F- x8 c G0 }0 N! t' e BYTE BlendOp;' P/ z+ z2 l) Z0 H
: I; L4 C% U. S* c8 k# C% z
BYTE BlendFlags;//必须为零' p/ u1 Z% b" P' j' H
0 D, O6 e8 C: B+ W. L& u5 s. ? r
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
4 ]! s+ t7 \- r$ x2 M- F1 H# P' ]
6 s& H: n9 A1 C BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
6 y1 O! G, q* D% o) r K# O9 D& r( v6 |% T
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;$ `; N5 z L( r2 h
" ~: U" u5 T8 D4 r# h+ m8 _% N+ C 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。 @) J: _" s4 ^6 c O) x
J5 i" y; ^. d0 N/ R: ?: C
% \+ t4 o; o0 l; R 编程实现
) Z8 v E! o5 t- h3 @1 m( m, R+ u' E% B0 d
3 J1 [ x2 u* v; r+ w
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。! t4 K6 P: K* c
/ F: T, a! _3 I; r 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:# r. q4 Y s. _+ S9 [4 X, I5 u
' f; j) G+ s- {% P7 h6 H+ I class CWipeImageDlg : public CDialog
. ~# ]3 N/ `$ c
2 Z% h" k8 f L% S {
4 @0 ?) ?$ m( x1 S! E' V- X
6 ]6 l( q- {: j5 e" ] // Construction0 D5 h+ Z( A A! P/ }
$ o7 D, @; E/ \
public:
7 C5 p& c5 _6 r0 m% A8 c
9 J! B& u2 P ]& Q+ h BLENDFUNCTION m_bf;8 q/ e: R, F4 O( y& o' d
, Z/ @5 ?7 A, f9 G) T u CBitmap cross,lantern;7 O8 H8 V: o5 L5 ~6 i
y( X( W0 x0 F BITMAP bmp;
! f( _) ~2 X: | M* H* Y
, b; x$ r) d' d' o$ z9 s# F: D int bmpWidth,bmpHeight;
( {: {. {( q* G; [' B6 j* a% b
, `6 n8 `9 s- v! m9 k& } CDC dcForCross,dcForLantern;# v6 p7 C4 U# F, k8 l/ q6 Y2 \
: P0 l( K6 w7 A
CDC? dc;# M7 `3 J+ q! O1 j% }
8 k* q0 Y+ ~$ `9 [; H% S$ U
BOOL bShowLantern;# H: c% M& O8 s) N! |
$ `7 a2 |" A2 Q) N; T
………(系统自动生成部分)2 R) V6 f( j8 I
+ q8 f( A, P) H& e* j
}; 6 }" e6 T+ G, I7 _
: s! m1 Q4 m: l) S- s" n U 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
. E: Q& H8 t o# Y
P8 S: s; D+ e% S& G4 [ BOOL CWipeImageDlg::OnInitDialog()4 A" E- }! j( A7 X
: T6 @. B- k. K7 ~ {5 H6 E9 L4 x f5 k4 d
7 F- q3 y- H7 d4 b8 h
………(系统自动生成部分)
( | r% J4 w3 K8 Q8 T9 Y( X- m
// TODO: Add extra initialization here
8 o6 K# B# j% S S* {- h9 R
) G$ h5 A" Q8 {$ r) S2 W1 h3 l //初始化全局成员变量( I6 Q1 g" r. z( m3 _7 e, G- h8 c
( ?) n2 f% L& \( o8 J this->bShowLantern=TRUE;5 f3 Z% d, { [9 \
4 ^. D. q0 I7 V! p m_bf.BlendOp = AC_SRC_OVER;
6 Z. F/ z: w8 c% n; t h5 F8 ^) h7 o- n
m_bf.BlendFlags = 0;. m# W- Z0 h* Z4 O) [$ V6 Z6 H
; s. g% O6 ~7 J2 ]6 l3 l) `$ N' S3 j m_bf.SourceConstantAlpha =10;
1 k9 b, I, U5 \4 ^8 y
4 ^, U' J' J3 ?9 g2 B3 E2 z m_bf.AlphaFormat = 0;
6 B# @' o' T$ W1 i" q; k. M6 R
! d6 c+ w9 n) D9 U! i //为节约篇幅,以下代码中略去对操作不成功的处理代码& y$ }8 ^0 U0 h* |
" n2 s( T$ g6 B, Y4 A- K+ g& g4 }, r
if(!cross.LoadBitmap(IDB_CROSS))
: g% | i8 V+ @
; g9 e* x: V# {1 P {
) {+ T `" M, e* [8 z$ r. e l' [2 K9 ~. a
AfxMessageBox("装载位图出错!");
3 k8 ?+ o1 t- o7 ?0 z* Q. }
2 G ^2 S3 t. D. | return FALSE;
( b+ s' y. G% \- t. ~) f2 _; @ G" j- N% |/ Y: C8 C
}% }5 F" l' v# ^% N9 s5 _
! W$ `+ j* c3 g$ d2 p
cross.GetBitmap(&&bmp);, i: _5 K( c. S h
7 E/ d* d9 ]9 K8 U: J
lantern.LoadBitmap(IDB_LANTERN);* s: d% m. B2 i+ R& A
* f0 @( t5 l: a# G9 ` X9 V cross.GetBitmap(&&bmp);
! h' S# C2 f& k o9 ~; k. u6 \
5 Z- J1 t& J m J1 I* z& x8 C- m$ e //获得位图的大小信息. o9 Z ?) f5 @# y7 y4 M6 o4 {
" }' [# X% T% a; ? bmpWidth=bmp.bmWidth;
" y! C% j5 Z7 k z6 \3 R, L/ Z
9 W6 `8 X: [; Y% S bmpHeight=bmp.bmHeight;/ {. P" o7 F" ^7 R) v# k5 J
( Y# B1 E/ V* u
dc=this->GetDC();- b U" \" k, i2 }
! }3 F) y. R0 x5 ^* L
dcForCross.CreateCompatibleDC(dc);! u( O2 I0 B- T1 F* I
+ X; V/ O2 C6 E$ d9 A dcForLantern.CreateCompatibleDC(dc);
- o+ w# V1 m6 g/ ~, J7 H3 D
1 I3 ]: K( E. ~ B //将位图装入设备环境句柄; \& R' O3 i1 X( y+ p3 n; y4 _
# o# i" {& F( A dcForCross.SelectObject(&&cross);
9 P, a m) O6 Y0 U& v4 E
7 c- N d/ v9 h dcForLantern.SelectObject(&&lantern);2 ]- Q3 f5 R, g- @ x: d
$ v+ m0 D8 j! x2 k" i( B //打开计时器$ k9 D1 f0 g. U0 l+ X+ S! `# R
2 S9 B; y$ C6 i2 m/ C
SetTimer(1000,50,NULL);
/ F+ Z. d7 ]% u2 Z6 d- N. p1 h) t/ a
return TRUE;
! A6 V2 Y* m, ?- y* K" z) L1 \( j# o/ B9 w8 h) }
}
0 l. [( D( f4 O: s* Z( U8 `0 e
, ^9 ^) O) I4 r6 _8 W8 L" @ void CWipeImageDlg::OnTimer(UINT nIDEvent): E8 d. s3 [. e+ X5 T+ `
$ d6 W/ a! w3 s; g/ k$ Z" I
{; n! l0 r. E; A' @4 G2 R
8 W9 y! \2 V9 T: y* _+ j# X //图片透明度每次递增5点5 G h! s5 O. ?: b. b' A, g3 U: d
( B8 J) m9 D G% K I9 `5 d( D m_bf.SourceConstantAlpha+=5;
2 n/ g% O# \% F$ S v+ x% U: g7 G/ r8 g
//当第一幅图片完全可见之后,显示另一张图片% H' Y$ n& h& H6 A" E' }2 j
5 I. J$ b$ w5 ? j. P8 b
if(m_bf.SourceConstantAlpha>=200)9 `$ n# T. l$ C/ O+ n- i; w
# e6 x+ t" Z6 ?$ n$ _ {
# v+ j' ~" [9 e6 K$ W
+ F7 q) @1 ]4 S) v& |8 V m_bf.SourceConstantAlpha=10;
9 F# T+ S% Y/ z$ L1 K/ G3 ]0 o
; O& z) c% U! l5 i/ v //将bShowLantern做为显示标志,确认应该显示哪一张图片
- J0 i$ x1 j& A8 B2 x5 w. s
' H8 D) u/ l0 Q) s) P) M x bShowLantern=!bShowLantern;3 q) b% X0 ^: j/ c/ r
" n. B4 g/ h) t" Z
}
( {! ]& v4 K l) i5 V) _" W) i* q6 X* p' w9 O, r2 e. Z
if(bShowLantern)* _# y6 ]; y. r! U S L! E
* _9 ^/ y3 k k) v1 O! s {
4 S, v. O+ o% v) o* }) \0 w
. s/ H4 F. W5 a //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
+ u" |0 K% ?3 c& u5 K7 ]5 N8 p7 Y/ y9 v1 F! y( @9 Y
}* i0 ~2 }! u r0 t, g
1 I3 M. V; R, r! \; K
else5 j: o8 z# L {0 M- L( t- M2 R
- g5 \' G3 n# a G) K8 o7 b; T9 m* o
{7 L6 N$ b" [2 Y9 n5 r
! j. Q' C# }% Q, ^0 V //按透明度递增的方式显示“背景”图片5 j2 [; w5 `4 h6 z# F! h' {
& d; X. T, P Z! ^4 t- K5 i9 V3 ~
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
+ a; B2 G# n% [4 u4 \2 k+ x4 Q6 I, r- S9 ` ?
}
: H5 P" N. F2 E9 p
7 D. \* O- P! o L% t$ w CDialog::OnTimer(nIDEvent);
: z6 o" [( m2 U" p9 t2 A8 D1 q; M/ m7 {% n0 O1 t8 b& V5 F# @
}
2 Y! j j9 N8 ^
$ w: G& l0 z: A; j% i% Z0 n
7 Y% f$ v# r$ l8 m1 a* G e9 { 编译说明% ?" s7 Z i+ R$ H
4 g" g* e% [* w: x' }" Y
, j8 [+ ], K! f% \6 J. { 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|