|
|
周鸣扬 V" N! v# K w
, M4 Z3 g1 [& t9 B$ f L. w
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
, ^; L2 ~& u) ~- l# @9 @) s8 d
- \! T+ K% J X3 U; a7 y7 \2 L$ [- m7 x$ T$ f4 q8 T
解决方案
! Y. B& g' F; _" W$ R" a; P& D' H. @) R+ x
& c4 Y% G7 q+ S1 `; I! I8 s 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:3 K% a' ?% w" z- n8 v. G
. k+ F0 Z9 W' `: s+ X: B% y; T
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
+ p5 D# C( K N' o# k' }
: A# X1 y4 q5 \" `7 k- `: Z2 i Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
* |" R5 q5 S% R! c: s
- n1 u8 v& I0 w3 p Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue% l( x% D9 E9 l! ?
! x" y: T3 h+ _6 ]! |- b- h1 k
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
$ T3 h& b- o, V4 ^. w1 g8 f$ y1 t5 i0 r- e( S: ~' P
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
) U0 J* C2 h0 N i8 B' a$ X5 A4 ^: K# |9 U$ ^
BOOL AlphaBlend(
2 E4 I$ B9 w0 I/ c( I- U y3 s$ N) P6 E1 m
HDC hdcDest, 5 z. R, S7 H3 e1 @, G9 ^/ g
# J- s9 F, V, X8 y1 j
// 目标设备环境句柄
7 x5 Y( A3 |) I% h' Q
9 a# d; k/ u9 Q. s# @% z; R1 M1 P int nXOriginDest, ) {2 h. A% J: |! q; X5 E( w
* S+ @, H& G8 T6 q
// 目标坐标x% W1 b. G" O' [7 L" ~$ G
3 x4 M& p( h, N& r' i7 q$ l
int nYOriginDest, - M8 g; F! c* N* K1 _
9 C5 `! l) [, ]7 p
// 目标坐标y' K( {% I" b( h7 k( p
, p' v2 p6 O) _; d
int nWidthDest,
5 K4 q. c0 p; y$ ^
3 H/ ~$ T2 J( V" a t# B6 F // 目标宽度
6 ^: h0 [9 m/ y0 T
0 I7 V& k/ X. d int nHeightDest,
b9 N3 r6 b5 ]% P, p9 `: f) g/ a; h; ~# T+ ?3 h2 k
//目标高度1 \7 p$ ^6 f2 Q+ {; _7 T- A9 c
6 x& Q4 E; D. |) ~; P. Q HDC hdcSrc, 6 k/ v- N. R& [) d4 U0 s0 j( u
$ B( g# e# _+ c# Z, B) i //源设备环境句柄
0 [: S! u- C- H4 s
7 s9 C! B% M' }; y int nXOriginSrc,
0 D" y( O! e8 K& i, F3 `
$ S% t) b6 S! R3 e7 v // 源坐标x) s! ^( i3 }; Q0 \- ?2 Q: Y3 j0 q
, {$ Z. o' a. `" O e int nYOriginSrc, - ?8 B: s/ [6 b' d# o- [
1 l* g4 j% \. _7 N* S; p // 源坐标y" }0 U5 K; \+ L5 {! G1 \
/ I/ U+ s3 g5 x6 Z* s5 k) c7 K" |% \
int nWidthSrc, . W- ~" g3 _- M) F, V
: G2 h2 s* D5 b# q# S1 e, ~# g: e //源宽度1 s7 g$ j! z- G& S
+ G. B& R7 z2 F \* I1 ^
int nHeightSrc,
+ \4 v+ f' r/ L. ?8 D" A* D: |) T" K2 m4 s
//源高度
6 M% O; h; \: O! A$ e! ]7 k+ G
8 e- \: ]9 D, i9 g3 d. k+ D BLENDFUNCTION blendFunction
$ u. T2 d' P% C( i" N, C8 Y1 l* p: v( _4 ]! F* p
// 合成方式具体数据结构
1 k4 j6 a* |1 N s8 `0 j- z. b) f# p9 e
);
- P4 ^/ k2 ]' E ]: z; i: k0 R7 B
# @+ v8 E) r* h& F" c5 y" [ BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:6 Z! x( Z# u I; B
2 `4 ^7 M9 O- q2 r" }: s, T) x
typedef struct _BLENDFUNCTION {; j8 w+ D6 r. x, x% Z
. A8 Z# l& I2 H- f- V: ~
BYTE BlendOp;! n+ C, `6 l' `; T0 N/ i
5 Y2 g; U3 k Q6 M! o BYTE BlendFlags;//必须为零
7 A H& P2 X. t& f3 q3 m$ |5 m. L/ P C: g2 e4 ~
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示- p4 a, L$ e1 B: c/ u6 l
) c% F) y7 U6 P4 c BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA7 X' ?7 G2 |& s5 C' B* V9 r6 X
7 q j2 }3 r( U9 ^. \- e# l }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;9 @- s4 K; I" {, U7 E, u. y
# `: c4 r3 j% z. M$ s 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
& K5 ^3 ]. }/ y
( {: \( X/ n9 L# O* Y
) z" e0 i& o: R 编程实现
% c+ E; e, o1 s( C6 V4 H+ s4 L. c* s/ C E6 p
4 f e& {& R4 M9 W9 _, m1 y 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。; q/ E. a- g& V+ t& Q: x
2 r' Y7 ^9 N# b 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:; y0 c6 X" S3 P7 m* Q6 W5 c
! S# }% T7 ]5 y1 f# Z( p class CWipeImageDlg : public CDialog! f& O" D9 _& Z F+ ^, M
+ c1 W& G- y0 Q( h3 P' X6 I
{
/ v3 W/ U/ o8 K( ?, Z. t
) o9 w8 S# f# V, }5 \- \. q // Construction
3 y0 ?! y$ O* A! u4 b
. H& b, d6 J6 v& j/ C# K public:2 g4 I2 R0 `! s( @" `
( V$ p- h* s, ]' H$ l. ]4 P7 T BLENDFUNCTION m_bf;) b# v F# _3 h
0 o4 E7 m/ ^7 E' o! H2 f5 I
CBitmap cross,lantern;% w: Q& @1 s d. [; Q5 F# E, i
5 U- _3 e/ Q& g9 v5 j. f
BITMAP bmp;/ |4 W0 s6 b9 h
8 h4 r/ h* z3 b/ {8 H" _" A
int bmpWidth,bmpHeight;
! g8 }% O4 n) H& \4 o7 O6 `0 P' ^/ B" e+ T4 O. m8 j
CDC dcForCross,dcForLantern;4 t& N) ?. ^% A7 ^
' K1 V5 T) C* S; t3 P CDC? dc;
2 V( B0 i- i- u* R) ]1 p% L: [" M8 _& @
BOOL bShowLantern;
8 a0 f. T4 H- g* d( N1 R( _0 V7 U& c$ r# |8 n
………(系统自动生成部分)
, b0 J. J6 u- x$ j4 y$ M7 T, s1 D' P$ O4 _& l* `
};
, i6 K3 r% n8 n9 F6 [' w) P" z( A B8 ]
9 g0 E7 V- I6 M z& Y5 J' I9 G& z# O 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
# R0 L0 w; p1 y ?) w% A
5 }6 t- [ G; L( [ BOOL CWipeImageDlg::OnInitDialog()
2 j! i! I: L( J4 n" a( k( j
7 a' N$ }/ D$ S4 v* { {
) V6 O I0 k, E9 a* K
7 Q2 d+ t5 v c; t3 b7 i! u( s ………(系统自动生成部分)) l4 x d6 K; _. k
- }6 V0 r6 i7 a" Y: T U4 _
// TODO: Add extra initialization here
, w& q$ P& Q; c6 s- U) h' B+ }" P6 T$ t5 S* ?# D) a2 b
//初始化全局成员变量9 N6 I* Q3 p; u5 r
" ]# H. r5 e3 c3 |! L" } this->bShowLantern=TRUE;
7 X" n2 s0 l0 \ m) A6 e' H- {' l# h x- r
m_bf.BlendOp = AC_SRC_OVER;
4 U7 Z j/ V0 ?7 ]
4 g4 ` ]& x. b& M* } m_bf.BlendFlags = 0;( T0 o4 o% M& L3 C9 d" ~9 ~
8 h6 j/ Q+ a- {( d: m$ b: J m_bf.SourceConstantAlpha =10;) n$ X8 p. v k4 @, F
: J' c c( M, K
m_bf.AlphaFormat = 0;
$ _! Q: O/ U+ L/ v5 T% @
0 X$ j8 k8 i0 m# \. Z. L( u //为节约篇幅,以下代码中略去对操作不成功的处理代码
- U4 {, z# g3 G. C" K
( i' p/ o% ]+ P+ X# F if(!cross.LoadBitmap(IDB_CROSS))
1 J6 o4 x' W6 ?! y* Q7 w1 E9 O" T( N9 c
{: ?- u. w. [, A
1 I$ e+ U1 ?+ z2 o
AfxMessageBox("装载位图出错!");
5 d' v8 }7 \# r: r w m# S& r: C9 Q! T4 x! v) Q
return FALSE;
6 \4 i4 G$ i7 `5 |
0 u, X& `# R0 Q- U/ M }
* D6 s: T! ^& B5 H9 D' E; b5 ^+ ^* ?2 T7 ?' A9 o& r' [
cross.GetBitmap(&&bmp);
0 D5 E" g' ?3 H& e/ [9 L* t- \" f/ `+ Z/ M* w
lantern.LoadBitmap(IDB_LANTERN);
5 t$ D/ E5 W1 I) Q- B1 V% Y$ M( B6 p) i9 x, c. l4 H, b" Y7 x( \
cross.GetBitmap(&&bmp);
5 R4 r; P' h/ m _
$ r9 m1 z( ~3 O- T, l/ Q //获得位图的大小信息! u: _% O$ b1 N i- c
% G5 w. g/ \# y: q7 O; z4 q
bmpWidth=bmp.bmWidth;9 g: |; ]8 w8 f0 n; A0 T8 P6 v3 d
0 v7 Q* L! r; {/ J
bmpHeight=bmp.bmHeight;
4 m- S+ J, _. a/ x n
2 s/ N: e+ [8 ~ dc=this->GetDC();
' z, L( V& t4 |
5 ^/ w7 C2 I7 {1 n& f dcForCross.CreateCompatibleDC(dc); A! g2 p+ Y+ d* a; Y; m e$ l+ S/ e5 e
* X+ t5 d1 u: V4 V, I0 d* i- U2 h
dcForLantern.CreateCompatibleDC(dc);
6 R9 X8 _2 E0 ?* j% J) n' e& I" v! s7 i4 J0 Q) F
//将位图装入设备环境句柄' h3 z, D- t3 r. D' ~0 X' p
, T4 t; s8 ~* v3 N: z; B dcForCross.SelectObject(&&cross);
- s* |8 x2 b' \3 |
1 j6 `8 V' M6 b1 a2 z- t dcForLantern.SelectObject(&&lantern);
! B# @8 D/ W7 ?$ o' P! A" s4 P8 T$ G0 A2 w) f0 ^
//打开计时器
$ g% Z+ @' o) {' ]7 _; C9 S
5 j, ?7 r. J; x" Q# Z$ \ SetTimer(1000,50,NULL);
H7 d& Z( M' d$ i# R9 h$ m! H
' D" n! m: s( _; r. Q4 ^( w0 w return TRUE;
. \% A9 J! b5 m, O2 {7 S+ j: K: `$ `' c' ~8 N$ X
}
" C$ r: N8 D! l7 b7 F" F5 J3 {. q O" s- [0 o
void CWipeImageDlg::OnTimer(UINT nIDEvent)1 r( _" q0 I- s/ X( t& u' [( U
" Y. N' o% w* q0 Y# e/ ]! Z {6 Q* D% Q& ~" b& n
* }* _& q$ n. K1 t/ E o3 }1 P/ H
//图片透明度每次递增5点
' u! m7 |& _; A$ j( q0 j. D1 I* J/ P( f
m_bf.SourceConstantAlpha+=5;1 y( \0 ?; S' D' I& G
; `$ |# Y1 P. A1 R* ~ //当第一幅图片完全可见之后,显示另一张图片
% S: m7 @# t# e/ ^8 q) K4 F. U- ]! {3 ^. G( [, _ q3 |# o
if(m_bf.SourceConstantAlpha>=200)
* g. L& o, y6 C0 V& R
' T# w( Z5 d& Z {
/ I5 y; U) _' ?: D; T3 Y
/ x0 O9 Q8 F% x9 x9 D4 U m_bf.SourceConstantAlpha=10;0 {( z* [# D4 K* G1 C5 g9 L" e z! Z' i
& L7 R$ j/ Z- z1 p5 g) O
//将bShowLantern做为显示标志,确认应该显示哪一张图片
) J: q& ]& C4 T, n6 P8 Q
: z9 ]$ v! C$ s. _- m bShowLantern=!bShowLantern;
6 O& W# m$ r: x3 O4 i3 w
6 x5 B# k" i8 m: n0 I) s3 A$ o }5 {* Q8 G2 W6 U7 d& ]4 C! o4 O
1 j" U% t: v1 D1 h8 X# y if(bShowLantern)
5 n# U9 s' I. L x" n' A/ b$ N }' Z3 t4 |; T
{
8 P1 X; Z+ h) K- X# e$ @7 d$ \
& u% r! x* P1 ~8 y O$ F) k; M //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);# k* u* k: g7 {0 P
! m2 @8 {2 g0 ~
}
, ^8 v- o% `! w0 I/ u+ I6 e1 K
1 i" J% h c+ h4 v5 A& l! j: ^ else( K+ k, u& O" A
' M: N) d8 k% H {, P- w$ s/ D$ L4 O3 Y# b
. ]2 y* `" [0 e/ _) I //按透明度递增的方式显示“背景”图片
* i( f4 p8 Z- C7 @7 v7 B5 Y; ` Z+ f
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);( k+ N0 ?* t$ T& x8 |: p, L
; q: l$ ~' I6 ^4 P" ]6 o
}8 M, \. U: ^! M* k4 J
, R: ?: N3 \( z3 A
CDialog::OnTimer(nIDEvent);# f* l+ q. s6 j7 L/ M# |
" S" L! e( @4 O. r b8 B. H6 r }
$ ~! [( e6 Q: ]8 X" b, A1 c
* c. n6 t- R1 u1 q0 o
& d# j1 D, b! o9 j0 k: e# k; t 编译说明
5 ^$ N' u3 K! ?1 i) n. b- @2 x; E: M" }# e$ [5 P8 t: r+ p
5 }* a2 @" O% r4 t$ v& J2 E% h
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|