|
周鸣扬 * n7 K$ U$ z' R$ V( ]2 k9 O. z
" ~! ^$ L: ^8 a( G& J# C' P 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?2 a1 c7 W' z3 O& M; [
8 M: s5 n0 B, J8 D8 w
4 e" n9 a6 D9 y4 T 解决方案) F. T: b. I: f- w3 W& s
8 h4 a9 }3 z+ ?
8 O2 I$ D/ Y6 }% {2 d1 I9 y5 f! J
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:; y/ J5 t3 H% F H0 _
2 g7 i8 U: y9 w
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red . A# C& L- t% }5 ~/ {
4 B4 H8 i1 R' d
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 1 s& A( _9 f5 ^ ], c
8 c; ^. V0 l8 X4 [
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
. A+ L- J, F7 ]. ?
: o# @$ c% Z& s t 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
. U1 m1 h, f. j, x6 {3 {5 E7 g: T" q. E( \
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
8 l" D: C/ a2 M0 {: `! P+ n6 e9 N9 V8 u& U- A8 V# z; Q
BOOL AlphaBlend() I* X( I, K0 P4 q& H2 u# R
9 w( X5 V. u6 g* x
HDC hdcDest, & `' m1 q1 P9 Z# s
$ h- R$ Z E' R; N* H1 {/ m# @9 {$ n // 目标设备环境句柄9 _$ s/ U* I$ [! c( D
& w, |3 q, b9 y2 q) @
int nXOriginDest,
3 i0 h3 \6 P- f# A
2 K% Q: D, A+ \% K3 l' g5 `2 v/ a // 目标坐标x2 w7 W$ d2 K9 @5 A, M
9 w; X$ i' N; G( R9 z( q( D% i
int nYOriginDest, ( G% m4 H2 [! X3 [
2 j, `+ h/ N" U6 I
// 目标坐标y
1 c2 {! A; r3 x% n
$ w5 W* Z7 [- ]% r$ D( p# S D2 b int nWidthDest, + b8 z' }( {& ?2 I
: R) D' n8 J# X# a2 b // 目标宽度! c+ u& M k) p$ }+ Z7 ?
" i* G H, R2 }' O
int nHeightDest,
; b+ I- c. d( \* u: G, N
* Z* E' ?" F/ l( t //目标高度2 n+ s2 g, D" u& ?# U/ Z
( Q) H7 o Q4 r HDC hdcSrc, & s- W1 w/ q2 y8 x& e
" X. m& r, F% W/ [
//源设备环境句柄
6 o5 j& T' ^/ @* ~5 w) F0 t2 j m9 w* L. h% v: P8 ], [- T0 B
int nXOriginSrc,
+ X2 H3 h+ s; \ w4 a3 M# e2 x; Z- w5 \/ L" d: M1 F
// 源坐标x3 p0 d7 x \% E9 E5 q- V8 r4 C
5 r5 W y- D# u6 t int nYOriginSrc,
* W" ]% B0 W# q0 o+ g8 I3 \3 v7 C D* d+ l7 `; R; h
// 源坐标y$ `5 D- \& L( l" \# F$ g
5 w @8 u/ Z4 K9 x
int nWidthSrc, ; `' _! M6 v& K! ^% e$ O4 x+ b
, h6 Z! x( ^! t( H3 M0 b5 r //源宽度
0 T/ r, t; ?2 k/ C; X8 U. A1 k) E% y5 c% K
int nHeightSrc,
8 y/ c1 ]5 i4 J2 C8 ?! C: q0 l6 X; O j0 u+ w
//源高度) s9 M7 Z. B+ D/ l9 D$ v9 t a
$ e( w- O* i" e h3 V
BLENDFUNCTION blendFunction
" i) D7 d$ n: c6 {: b8 v" p# b" x# l" S5 c4 K7 j; H8 I
// 合成方式具体数据结构
4 X7 z8 X9 ~* \0 t0 G7 _
( k2 p1 O+ q8 Z* J+ A );
1 k6 V) B6 Q6 f2 J$ B0 M% L! _8 u- I
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
6 m1 X- E/ W* m) J
5 Y3 O$ B- R2 L7 \: ?/ a$ R typedef struct _BLENDFUNCTION {: k; ?3 e$ q' d8 ^4 r
1 v( }! O. m2 h/ l BYTE BlendOp;. k1 j/ o, m& Q. y
% g, P+ y9 I- M( ~ l: c* c
BYTE BlendFlags;//必须为零 v# x: D* |; [* Q: {
4 p: ^+ B2 M9 A3 \0 ?" k1 [
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示/ V! x* o3 p: J
: p: n" Q6 L& x5 D/ ~( w
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
4 g2 p5 p. A" g& F! \9 U0 Q5 k$ g/ `, a" u
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;" a; }1 {$ _, W/ J! u; S+ J
' u$ A! F k9 g9 r- q0 b
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
% m+ ?9 ~# Q0 ]1 @7 _$ Q$ K, h
* N9 Z1 N- m9 }* K: P
- S/ P& w3 Z/ U8 l 编程实现
4 L: e8 H$ J$ h% y- k! v7 ^2 x+ r) x0 `% S4 w; W( w- T
+ L/ i7 t. B3 K 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。* a* g9 u" q9 P8 X5 }8 ^, s# }
C" N P# G& r! U' s! |; Y
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
5 F$ e; B+ c3 T+ p) L$ I" l
' k: {6 _7 c) O- v* ^: l1 B% f0 d class CWipeImageDlg : public CDialog1 r3 ~8 I/ I6 B+ w8 }& W- o/ S9 j
- L9 b' T7 m* b' ^/ h5 P {
$ W4 C" B- V& d( r' H3 `/ R, _: F \% n
// Construction
8 E( E& ^1 k: U1 ]: [( {
- m: \1 i) u+ Z$ q+ h public:
1 A( O5 u3 s( B* Q- W" `
, O0 r9 V+ w, `3 y BLENDFUNCTION m_bf;
( x, X+ x, p {5 x
& M% E. K% C* V/ l1 b CBitmap cross,lantern; \* [ T4 ~. p9 N7 }) J, ^
4 K& z2 G* O+ R4 S( V2 U
BITMAP bmp;
' U3 A9 C3 U6 z% [! X% z3 Q( H$ \9 g6 t( S
int bmpWidth,bmpHeight;9 h9 s N3 T/ @; i' E1 t
4 [) F3 q* u6 P) e7 l; U
CDC dcForCross,dcForLantern;
% b, g3 }( V9 ^+ R: V, s# d* |& x: B9 E
CDC? dc;
0 z& t7 A' c1 ]& L% m% ~
; o0 F. T4 S! y, h3 X; K4 { BOOL bShowLantern;
! h: o {8 r# a j7 f# a
# h5 x, u8 s' z0 @: | ………(系统自动生成部分)! }1 n& _- i t% H
2 _& S# T& T$ I- a0 Z6 s
}; 7 x* ~4 x+ m$ {6 G; u# y
; }6 w% ?7 w) E& D) `2 w 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
+ A7 h5 E2 _$ E/ O7 f" G1 o# z0 z0 b! z4 N/ W0 |2 h3 d
BOOL CWipeImageDlg::OnInitDialog()
, q. }* P- R+ y& C! n/ d2 `' l0 N! ?/ `1 m
{& O9 s7 q! C+ `: l
: @5 w; ]" B% w) `7 w
………(系统自动生成部分)
- h$ a6 e3 m% s( S9 x2 I" A S/ p' s0 a2 I1 B
// TODO: Add extra initialization here
/ s& W7 Q+ m6 N5 Y
1 R) n/ M5 C! s8 y% }' j, [7 ` //初始化全局成员变量
1 `! S' F+ u* S3 {4 n7 }6 F) g# ^0 a9 V1 Y! s5 L
this->bShowLantern=TRUE;
2 g- k ~* s6 E2 T% V8 Q
& g( `8 a5 P( j" ^% k2 y5 ]- c m_bf.BlendOp = AC_SRC_OVER;) K+ h. @' K) G/ i/ ]2 L3 F. a
1 K* O8 P# h. i L6 L
m_bf.BlendFlags = 0;& \4 q5 q1 k5 P
; T, b9 E3 S$ e0 [2 ^
m_bf.SourceConstantAlpha =10;
1 j. t' c+ E0 R P0 i) V( W' [; }$ z) ?% N; z
m_bf.AlphaFormat = 0;$ _& w+ \. Y8 L/ h
0 K7 t* ]' O$ \ p" b //为节约篇幅,以下代码中略去对操作不成功的处理代码
) W! r: W5 f3 W% H7 y# h' L/ b. c
5 C! ^# k5 m$ H, F+ _6 @4 o8 u- | if(!cross.LoadBitmap(IDB_CROSS))
7 P* n) t4 i" l1 U0 H
" ~" N4 n- K" j) Z {
0 A2 Z6 A7 Z- c8 A( V
0 b/ \, N- k$ e3 v( K# s7 i$ ]6 V AfxMessageBox("装载位图出错!");
8 o4 i9 W4 C/ ~5 l5 t- h
6 _. Z0 ^. D& ^# n return FALSE;
( A0 `! t: H- r" Z3 B6 L& q: a4 {4 a$ T
}
; v9 D* q* v) z: w d3 r/ K% A; i3 R
cross.GetBitmap(&&bmp); ^, J3 s) Q& W4 H; v8 h
3 {- h+ Q+ _ M8 M- a lantern.LoadBitmap(IDB_LANTERN);* M& L5 a1 v" b# B
0 d- U. m4 U3 u6 L: J
cross.GetBitmap(&&bmp);+ B. ?% f) ]5 ?2 c
7 C+ P' J2 }9 ?
//获得位图的大小信息 _3 e. o( {5 @- `: R
5 X' f* v/ F& _, l: ^! A7 r7 O
bmpWidth=bmp.bmWidth;$ ?9 j8 @# D! W+ V( P8 u+ \: [: v
2 O& M+ j/ J1 P, Q- V! u, C bmpHeight=bmp.bmHeight;! z Q2 h, n" H+ i* {
% ]6 V% `) l% {- y: j& _' { dc=this->GetDC();
; D' O( J7 m3 |1 ^9 }
0 C. v1 {3 h1 X' t dcForCross.CreateCompatibleDC(dc);
1 A+ h/ n- q0 r& Y7 q8 f8 [* v% p0 ~. h9 D5 A/ X
dcForLantern.CreateCompatibleDC(dc);2 y2 ~! P$ @- r/ N( w2 k: [
- l5 r& {( p! M2 ? //将位图装入设备环境句柄4 k' j4 b: o8 D/ n1 u4 C( D/ T
) h; n$ b+ z+ p8 p* m' ]
dcForCross.SelectObject(&&cross);
) B3 G! s6 e' @
5 o$ H- @" Z" ~- F( o! W) } dcForLantern.SelectObject(&&lantern);
9 A$ _. `4 N1 z7 `- H8 } }
( b. i( b$ k; y& S* e" W X //打开计时器1 a( ?( g! |7 s2 u& g) [7 @
# j; F$ k& ~8 n) O9 P$ u: m SetTimer(1000,50,NULL);' l1 J1 a2 b& T' j, u
- b8 m: L0 ^% p3 z+ H9 S2 z
return TRUE; + H/ f2 Q% l8 X7 \8 p7 d- a4 k/ w
4 J7 X0 H9 |. R+ w, S6 c+ ] }" [$ G5 t; l7 p0 h/ K
7 N; U- G* T3 q1 S
void CWipeImageDlg::OnTimer(UINT nIDEvent)
6 q5 A# X2 H" z4 Y5 l% e3 @/ Z+ ^: }" ~4 n; v+ a, }# p
{
7 y9 s: N+ o" b, T# X; A3 ~
! M* y9 S: o, u* \" m- }& H //图片透明度每次递增5点
2 u7 o- m2 D/ h G
6 P2 {7 w1 @' l1 s( e2 n m_bf.SourceConstantAlpha+=5;( }& p! p0 ^/ {. @0 d1 ]* G4 I
2 T) g' \8 g: J //当第一幅图片完全可见之后,显示另一张图片
6 \8 _5 u3 v. y V9 d3 @- l( u& M, }& [* }) c7 V4 L# q
if(m_bf.SourceConstantAlpha>=200). y( k; j+ W3 i' r
; L+ b7 ~9 ^% ?5 s8 r
{
/ h$ @0 o3 u3 i2 @7 C" P& v E4 [; H& K+ |* {* i9 O5 s [5 f
m_bf.SourceConstantAlpha=10;
: Y5 Y2 ]2 [+ v6 I8 I- N0 H8 E# d
//将bShowLantern做为显示标志,确认应该显示哪一张图片
7 {8 a) K* S! I- Z' |) G4 y& {. N8 d+ I
bShowLantern=!bShowLantern;+ [/ ~( e' d( |4 r0 E
+ \2 }* d+ j2 U& T8 z" k
}* S6 N9 |: Q9 ]4 S* `3 V
# U1 c- V9 X. L7 B; l& N. w9 u
if(bShowLantern)$ V1 [' L) m) |9 Y
i* |9 K$ m8 X% @- g% ?& F: k( Q {
, j8 C- F" } t. U' N
: c9 C+ b1 H3 T. p6 V8 k- a //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
- U4 t" M7 f6 C b, |
% k3 ]9 X6 W' [0 O! x8 @& D }- C7 R; m! p9 z7 q
# V& h: A' e1 N6 M else. E5 Z) W g, a$ j% F
0 j( K* ] b6 q+ u3 P+ `0 H {$ b& s3 L3 C( m' k# g
/ A. X8 r1 K7 W1 J0 j, v
//按透明度递增的方式显示“背景”图片
7 b6 |. S+ D( e8 m5 {* X3 P5 ^3 `9 ^6 ~1 Z
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);9 j: ?4 X, C. G, V2 c& k
! F* K! V% `2 ]2 U+ Q
}' _/ W% G1 i' [
, y9 ^2 w) L" e. B! a CDialog::OnTimer(nIDEvent);5 |! N5 g) u& ~) a3 _3 M
3 s5 j* y* u( ~9 O) ^
}
" C: x! I0 o! Y' o) N8 i& ?1 c" ?9 B& b8 @- y+ s' ~5 }) A) [
5 X" r$ h" k5 c& i7 a) ~
编译说明
2 O3 _ j, T. o9 H% }# K6 v% Y" N6 t. ^$ p4 a& c8 D5 G
( ~# m- A( Z7 \, m( } 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|