|
|
周鸣扬 6 ^/ e( i& z3 N/ M- n
' u" D$ [9 H. k 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
6 d5 c. s0 T2 M \5 U- K
+ ?5 q% I7 E% s
' d8 ^; P3 ^+ t% x- I 解决方案1 F" i; Y' E3 H Z
) y4 R3 Q# Y' X, B& S8 V8 G
, v$ I1 x' K+ w0 p9 O 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:, i: ~+ o1 F% B3 e3 u
0 i- g8 r2 B: x7 W. E+ r6 x Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 8 n$ g" Y" R; t
0 _4 T& r( U) c: |6 d& {5 z Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green U. l* _1 p. u4 \8 ~2 G6 V
: r l+ |% l1 t: c [ Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue# M* k$ y& l$ M' ?8 |( K
4 }* M9 B; @* o
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' ?7 |4 e9 R8 ^! Q1 I9 _: }! l p' s0 J
( t$ w6 n: _! ]# G7 G/ A1 O: I# K 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
# X: D9 l( {6 u' |5 t$ |
5 r( I8 u; ]( `$ e% A. r BOOL AlphaBlend(
/ D* B* X9 f% G6 G
z* ?* a$ g9 K5 a8 _, X! W) d- A HDC hdcDest, + D- G/ p) z% G' Q
, V" s0 o0 n/ q& f
// 目标设备环境句柄
$ }9 z, L! n2 v' N7 u ~3 y, n, s* c, k' E/ R( K+ X
int nXOriginDest,
5 W: x* f: I" Y# e# t7 P
6 x5 X. {( N5 |3 K* ]* g5 ~ // 目标坐标x
3 z4 u" R; C( P+ K6 u% b9 l4 }0 Y/ k/ R9 S7 K( b3 U j% {& ^
int nYOriginDest, 3 [. U4 T5 p. L( J. g8 J* U
, @+ L4 q% I! U6 T) L. o" s // 目标坐标y
S4 r! `) t! U( v8 o2 {& {, ]0 o; M* C7 l/ P, f- l! O
int nWidthDest,
" ]& _3 e! M; N4 F
" {" E7 z R2 G [% D // 目标宽度
$ A. h( n9 V9 Z! t) L& }' A0 ?% c& L. i1 O
int nHeightDest,
& K; k8 u* |, a H* g. b9 q3 T$ ~$ [
//目标高度
- z6 P+ _: N. a" ?7 e# B
2 s/ K3 S; U+ h# k% c+ [ HDC hdcSrc, ) U! b# g3 n! H- R
: l' \+ C* N9 z6 l1 `1 z3 m
//源设备环境句柄9 ]' _# Y$ i+ {2 W2 O$ {
5 W+ B5 S6 z8 x0 G* f9 K5 j4 |' d! S
int nXOriginSrc,
* U# e3 n0 _. l* w3 r$ e( \3 X, i; i7 f$ Q0 J% e% f4 o. j
// 源坐标x
- H1 ]& @$ K: H4 p( g. g( u5 @* [0 F- f3 w w7 r( l
int nYOriginSrc, 4 p$ g4 p8 o4 L( e X) |3 C
9 j. x5 A- K& Z( w; E
// 源坐标y- m& z& S! Y. A; X, ^$ f
* m; f3 D2 U+ M, g+ m. Z
int nWidthSrc, + \! z. l. U8 f. w& m9 j' K
1 V6 X: A S9 x) r
//源宽度7 n; E5 d& \8 O0 M# R
5 @. a3 K: A7 e( z- K2 G L int nHeightSrc, * b/ {1 Z9 s0 k. v D" b, D' M
7 ?3 Q% b6 T) | //源高度- E- d7 Q, W- @" u$ t
+ L9 y% L9 P+ O6 p1 A% I3 X BLENDFUNCTION blendFunction `+ t# n( X1 i1 t; t/ x% |
0 g k: {$ S" h b7 V6 ]( V; C
// 合成方式具体数据结构
0 ?/ n R" r; g0 \ }! Q3 B* r9 e' z$ E( D
);1 Y/ X1 U3 [4 i: d0 ]: ]
8 m$ X2 T2 L' P* V* S/ t
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:2 D0 J9 A7 R C0 x
3 t c8 F: h6 o: S0 g' x typedef struct _BLENDFUNCTION {
. @3 [; q6 Z/ D( B$ i
7 w% f' x: x+ c/ ` BYTE BlendOp;
8 M' e6 n0 V0 N; d" A
0 f( X" ^+ ^( n4 ~+ U2 [( j6 r BYTE BlendFlags;//必须为零
" d" n) ^) [4 J2 a3 J% ]+ I# x
& b( A6 G) b( W) x: J2 X BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示0 n4 l, g" Z1 R. p) k8 w/ v
+ k5 v2 ?8 f3 ?4 c, D# m$ _
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA3 ^9 L3 r: F8 k$ E1 Z! K3 {
4 M3 l% S- }3 r5 e( y1 r
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
x7 O; Y: M5 P; z4 R( H: S( F1 E2 ]. S+ c# f& E
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
- S Y/ G- E; R8 [8 |; ^/ Y* z0 T& B/ K% A+ r
! O B R; i' C9 N$ \) [6 s 编程实现4 r' s8 E# b; O% W
w* M9 C, |9 d7 H1 [0 d
z0 S" |# V6 |% U8 { 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
# x% _' S/ @5 m3 h) C! ?1 s" d& j5 Y" v$ A
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
/ [2 I; l6 L/ n
( s3 R$ ^4 O$ T& x+ t' M% i7 p class CWipeImageDlg : public CDialog& h* m! r% u3 X
F4 ~: h3 s7 e% p5 c0 V$ V7 D
{
: `" A' B& B* p& f; o+ u1 F8 @" D' j1 m. N+ ]* ~9 P$ c% t
// Construction1 W# M* F8 V9 ~' @2 {
. V( P# D4 l6 ?" J1 Q U
public:9 t; o* s- a) i
b- y, L9 u- J( t3 c BLENDFUNCTION m_bf;4 V. R/ I& y. w7 t4 V& X
! {! U: U( O: k% p+ T CBitmap cross,lantern;" \2 Y( W' D3 A" K$ ^% |
+ N% g7 G; r- r( U3 p3 c BITMAP bmp;" O# ^/ Y6 D' u5 I" y! k5 \( m
( ^1 p0 O! U Y% ?% x& B int bmpWidth,bmpHeight;& a W1 t( |4 N! _: x% w9 u
& b' g0 V6 p" @7 N5 ^4 Y% Q$ _' f CDC dcForCross,dcForLantern;
1 V& L/ v1 c4 q& w) a- D2 ~
( I/ O4 i& p p, K# O. j/ o CDC? dc;. z8 b( A) U0 m: f7 }
' c9 N0 D; R( }. ^2 ~
BOOL bShowLantern;8 U0 d+ a+ P* G1 h% Q3 F# ~
% N, }$ m2 p ~* M7 @% x5 `
………(系统自动生成部分)
3 J7 n" k5 e7 |$ p& X, v
( s6 p& i# a- g4 C };
- l* g$ @( `$ x% |' }- S5 Q1 a, `; n1 X$ j+ n' [
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:+ q, T, U- q4 O& E- q
3 S* `6 H* d: w* P$ h" k BOOL CWipeImageDlg::OnInitDialog()8 ^+ ]& ^3 g T0 O
# B- \( C( ]1 g/ N
{& s+ t, y: M% u5 ?; U9 K
" f8 M$ x- h' K* ?2 O' ^) I
………(系统自动生成部分), T9 Y. B* ~4 r- i% U
$ u4 o1 [6 V6 L8 H // TODO: Add extra initialization here
) [6 r* u+ s- Z; Z: M; |3 ^
7 h+ L8 a; e" ~6 \* n //初始化全局成员变量
' u6 g( |- ?' H7 l* u8 _8 V1 c
- q; h8 G: |- V! r7 ~+ J/ d this->bShowLantern=TRUE;( V" o0 r" `0 v7 b8 O2 ]' a3 ]# v
u g0 T% P ]7 e: y; ] m_bf.BlendOp = AC_SRC_OVER;
8 o4 i0 e; L3 _' j0 `
& f8 ~4 N0 q y5 T. O m_bf.BlendFlags = 0;6 U: h8 n, M# ?' n U/ v9 ]$ ]
- I. S/ j: e# M3 s5 G
m_bf.SourceConstantAlpha =10;
# c+ t6 k6 {0 _
* b0 A1 d9 q* @- Z2 a* T( r4 |' h3 D7 _ m_bf.AlphaFormat = 0;
, d5 d1 I& O& e+ K! T+ p
4 M. w/ A! l) Y( `# W; |4 F0 q //为节约篇幅,以下代码中略去对操作不成功的处理代码
# b' c, P- J8 m' X. B- W6 J2 v" j
J% O2 T, z% ?2 q3 x if(!cross.LoadBitmap(IDB_CROSS))) b! _2 q2 [- W3 V- N# o+ s
7 u" j1 @+ r C* h$ @8 `( _4 A- t/ F
{
4 {8 {/ R/ s( ^* Q F9 l
3 g5 d" o9 J/ s! h- \% b AfxMessageBox("装载位图出错!");8 S. T- B; p* a- I) Y* W
. J( d- e$ }& N+ H4 l* |% v( @% M return FALSE;8 F* q; ?8 c4 r
4 [+ N$ b+ t* P+ Y
}
( ]4 L4 `5 k1 ^0 {4 r' n* ~! I* Z3 f& K' c9 S9 `
cross.GetBitmap(&&bmp);
8 b% a& u7 \4 J" S) k& \
+ U: w k1 T h; X lantern.LoadBitmap(IDB_LANTERN);
e! u& g7 K+ \& }8 k1 ~
0 q0 H2 ~6 O7 M6 _* H1 ? cross.GetBitmap(&&bmp);
: I2 v. c5 c% H; Y) }: B5 P' G
9 d* P" R/ G+ B7 O' J5 [) C- X //获得位图的大小信息
4 y' ^7 @& y& J0 R: o4 Z8 C" z& O$ D) Q3 M ]5 l6 Z, Q6 D
bmpWidth=bmp.bmWidth;9 W0 x. O) }9 T
4 d2 D) R* }( }$ ^# C/ l
bmpHeight=bmp.bmHeight;
5 i8 p- a* F0 M \$ h/ k$ I, [* S4 b
dc=this->GetDC();
4 l5 U( o( |. Z4 b) w7 U) r- |0 \3 W4 C
dcForCross.CreateCompatibleDC(dc);7 o7 b& H+ R3 A4 G1 e" b$ w
+ H9 G: t8 A0 u2 l: E& Z* p dcForLantern.CreateCompatibleDC(dc);
9 y/ s9 ?7 Y/ X' |& n1 W8 |, S1 p& ]% b# C0 {
//将位图装入设备环境句柄' E9 P* a, Q( B8 w9 W! ?* i
7 w* T0 P) h3 `+ O dcForCross.SelectObject(&&cross);
! }3 q3 Q u6 A* V! N/ w2 [3 ]0 K: S, j% x: N$ k! r6 E* x1 A
dcForLantern.SelectObject(&&lantern);) f" G8 X3 ^' [! M
; G' C8 c9 n, T' x- a. ?/ P) ` //打开计时器
& Z0 M7 C" h+ i) s( m/ h
. c, S3 V. O5 F: w ?5 T9 n9 g* K t SetTimer(1000,50,NULL);
3 o0 R7 i2 _. {7 o
( k& J/ R7 D9 C m9 [: I return TRUE; 1 L2 A e7 t5 ~3 w- f
0 I4 I6 A& Q" j
}
& R' Y! @8 [; ^, X/ @2 w. a% ?- F1 l5 V- P4 q( k$ J
void CWipeImageDlg::OnTimer(UINT nIDEvent)
- R( C' t Z$ l" N8 k7 K) Q. r: E, i* {. D& A, n
{: I* \! [0 t; x7 [0 g* P1 \8 O# v
8 d0 v/ g7 b5 s8 I: w
//图片透明度每次递增5点' I- {7 k- m+ Y7 y
3 a2 E$ q& \; t' g7 m
m_bf.SourceConstantAlpha+=5;( p, B1 t1 Y% x; M: K! [3 Y9 w; E7 d" I
$ P+ U# g" F0 }: I% E0 k //当第一幅图片完全可见之后,显示另一张图片3 |0 R+ M) E) p* T m: v
2 ^% ?/ D+ S/ E& ~. i
if(m_bf.SourceConstantAlpha>=200)0 ]% I) T5 g4 p. C" k
# F% V' W$ X" q% P5 e {
, A% l+ T. A5 u- c/ U8 A
9 q2 e/ {* H4 z+ a" h' ?7 r m_bf.SourceConstantAlpha=10;
% }: v* Q2 z/ c+ S* l$ H7 p* i P5 G/ S
//将bShowLantern做为显示标志,确认应该显示哪一张图片
0 H0 x; j- M, x$ K' w2 [- q) i$ {2 M1 D% A; y
bShowLantern=!bShowLantern;
- s+ g! T- X. ?/ M, W+ _+ ^
; [8 B n% c; ?$ [; t5 A }
3 U6 X, I& w1 \- X" H; B4 L# P: [0 n! S* [' ?& i3 _) [ e/ G' H! U& Z
if(bShowLantern)8 b; v9 y) q9 r6 p. D
0 T" F. n9 }& h' A {$ `- k* S, ~$ z* P7 }- T+ v$ @
$ w- [) c6 y9 b5 `6 S$ O( X3 `
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
( y: u# r' Q. _5 N5 N" }
4 F- s# X2 F7 ^3 ]% H- ] }
" I: M% \$ {1 |9 C+ Z7 r" U& }+ |/ q& z) d" j4 ^3 n8 [2 r0 j* w
else8 ^" e$ \: R8 B% z; u1 x0 u
+ L! [7 X0 a; J5 r: ` {9 P& P# Q' Z, T' ^, _. u+ d! P7 `- ~8 M
) J( N( }. i. e5 ^+ m: a* O
//按透明度递增的方式显示“背景”图片( u5 G& l9 p/ M* K8 P
. K8 p0 u; l- U" H; g$ ? AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
: U+ {9 J7 V% U! h: Q
" _ |# u9 R1 G+ e }
/ ^/ m" [4 o/ e5 U& v2 x
J/ R" g1 M2 N CDialog::OnTimer(nIDEvent);
2 g3 w2 H0 [5 Y4 k3 W0 h; F l
" J# {/ @% r7 U; ?, \ }; ~1 o1 p! z8 P- j8 Y1 ~
; y$ B) o% e+ y7 Q C
+ e: o, d7 i2 ?" N 编译说明
# T) f8 D+ {9 r- C( _9 e& F; j2 ~/ b3 a: w( E
2 f+ f0 `/ c. x' z" n( s3 ?
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|