|
周鸣扬
4 l2 _+ Y! X, \. Q9 m1 e
* `8 M) _8 J2 {* _, X 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
& P; l; {" w* u b) V1 p- _; ]9 x
! u/ N# u6 C2 A* Z3 R7 a7 q. Y
解决方案
! I2 g o& N, ?. k5 D* l; F
X* w8 l" A& C4 c/ _0 V# W" u7 I$ t' }( B
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:& R1 g6 ]6 }5 P( l6 I0 ?: t
, [( T2 R( ]: X8 t) K
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
6 H9 P B: H$ b# M( @
8 I+ R6 w1 I0 a4 V: T( i5 l Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 9 U4 G& y6 E; _) U1 ?
' y. R# H7 G0 K: j; O Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue8 ]( |0 z6 k i
}' W' {2 v) j" M! p 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
W. Y8 f' Q: `* v0 J5 v+ S: W+ j; z2 j+ h2 A
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:3 _! A+ M" j7 y9 D3 ?- ^" W
7 U* p9 B& S! `7 M' z4 F9 E0 \
BOOL AlphaBlend(
4 m5 R" j" Q; t9 c- A" \
3 ^7 r' g- }! J0 z w5 i HDC hdcDest, K3 ~4 ?" h! X$ J- c3 _% y
! [2 P; Y# B, z0 i$ L% f+ \6 @( Q1 d
// 目标设备环境句柄
5 F2 t: b' U7 c% K% N9 X! Q2 J/ Z* t+ x% r# I2 D v
int nXOriginDest, 8 D+ z* X. P1 `+ Q
" ?! d# W: }, S3 ~3 u
// 目标坐标x
( s4 L' f; i4 }7 g
+ v- ~7 ~; @6 @' W int nYOriginDest,
$ s9 Q7 e) l+ r( E [7 P* E4 g/ C- T" m! p" U
// 目标坐标y
7 W5 e W- g# g5 J* g6 h5 w( q( \; q/ t+ W2 P) j8 Q @8 o
int nWidthDest,
. }+ z& s" `9 ?2 o2 U0 ~
; ?( t& e. o1 x% L! u, O! M( @ // 目标宽度
3 X8 n: |! |! W
) K' K% S' \( |5 x4 q; S int nHeightDest,
9 l5 ?$ C: @3 o5 p: l& v, i; L, C! W- ~# b- W8 q
//目标高度5 l2 L0 l* C5 ^. J
4 s0 S5 l6 i. M7 g
HDC hdcSrc,
: q& b+ ~1 y" p# j) B! x' f
" x8 a8 D8 r" i0 O* Q //源设备环境句柄
9 g. M$ |& B0 A* f% B
9 K5 e( G6 e' x, F9 z int nXOriginSrc, 3 B5 h# A2 f) n: z- g) q$ _7 J
* z* |6 G4 D4 w9 }* T V. U
// 源坐标x
7 I3 }( ~* S9 x9 y. M& w* H
6 N0 E( E* W3 |; M$ Y int nYOriginSrc, 9 H% w4 K6 C3 `' B. r: S ^
9 U D) i* H3 j9 Z( }, x
// 源坐标y
2 G) P4 G" @8 P- R/ u V7 o9 t) f3 u9 h! V' ^( |3 u! o+ |* _4 j
int nWidthSrc, @- @ H& J- Z% H
+ P% x" j) C) W9 ^( m' h/ w5 q
//源宽度- [" q2 {- U/ K1 x
9 k+ T* K1 v0 b7 \8 K int nHeightSrc,
/ \1 Q! l2 ^- J k
) A c9 e# O" H //源高度
6 ?/ a6 e7 a: @' X `: S1 _9 H5 A4 z. q& C9 v
BLENDFUNCTION blendFunction 1 z* d' @5 j+ }3 A8 b
1 A1 ^/ P$ ?2 |& U; I6 x9 q
// 合成方式具体数据结构
6 R! G R( G" O/ V" j) E1 z; q6 U
( D# `9 }$ {; R) C- r7 L );
; B& F1 x, V" @# a' W
" D* p- o" B2 f e" B BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:6 u* x% ~. B& ~! H$ v
6 R2 ^( j' C9 g1 _* Y1 }
typedef struct _BLENDFUNCTION {" H2 K; ]: D* {; R M
$ a8 G" g2 d$ S; `+ w" E
BYTE BlendOp; L9 M6 s; a( D# ~* j. z
' r! p7 d0 ?* q' T4 n BYTE BlendFlags;//必须为零2 s2 n9 l9 N( P& Q7 k% P
3 D! J P2 c9 T- a, Y& ? BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
& K/ r9 t( R0 B; D7 `4 N
$ `( Y t( p: L% \ BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
% T% t- [% V3 k5 Z$ Y/ ?$ b5 d1 O; _+ b- S
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;9 L6 s5 |7 d* `; m6 H3 ]
) s ^. a2 x/ X+ m& l6 P
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
, S' j, i d. Z) } E: l% ]2 b1 I |1 H# O4 G% Z3 K
" @4 T1 F* B4 g: E/ U 编程实现
3 n0 Z9 x4 o6 v" G* X3 h) V! m
5 |' r1 |! n# Z2 i7 I G# U- C' f8 C1 F% B, ? K o! |: |0 ?
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
( J; J6 A3 |9 j2 e8 a9 ^+ _% M, D$ g+ w- g1 |5 P* y" W( [: |+ G
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:1 K, G# ~% N- u R* ?, M
- ]; C, @( e" P! e! k/ @ class CWipeImageDlg : public CDialog3 D/ k" W. ]# L/ R
9 [/ E* c/ U; o$ r( o# Y
{4 D# `8 q" g, U8 D: y
/ c- C1 j4 ?1 }
// Construction
; f4 {4 R" P/ z' x: O& [1 ^: M0 @* F' q7 R( {) F- f5 l
public:% F& K8 v ?( S( a. _" Z
5 Z4 u; M% a, e3 u/ v BLENDFUNCTION m_bf;
: a) u; F _# s5 L9 u( T
" x8 ~8 c1 D; s) p: X( ~; v# r( G/ [ CBitmap cross,lantern;
( V7 {- G) O F e1 M: y; d, g0 Z+ _; S
BITMAP bmp;/ G# E ]4 ?- {
0 ?# X4 _$ ]8 G9 |' E int bmpWidth,bmpHeight;
; X; J% R0 m |1 n$ }9 D2 H# M% H( s4 l6 E( A# H, R- q
CDC dcForCross,dcForLantern;
: W5 \7 s+ ~$ t; ~# l) @0 n3 E9 Y6 ~# N
CDC? dc;
. U8 p) t3 i+ A: R0 |" \% u& j7 S7 P1 S. Y2 n8 T
BOOL bShowLantern;
@/ T* Z4 q& j m4 I- O9 J2 G' v7 @
………(系统自动生成部分)
' B) M. |" S" I1 d. k4 O% e8 v- q1 @! ?
}; & D& T8 Y" N! h A
& h) L2 k" c* d# C
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:) n# O/ s# G9 a, T
, p4 c5 ^ U! D1 r& ^
BOOL CWipeImageDlg::OnInitDialog()
" z& u# Z4 \* i; K5 d5 h; m$ |, N' f; ~3 t! d
{% C* Q7 P2 s0 r, f" ]. U
" y2 r8 W5 t3 E* Q
………(系统自动生成部分)
& K. G t) \) k, L$ Y5 L: ^0 @$ U) i$ G4 P
// TODO: Add extra initialization here2 b0 R0 H6 [3 \; p
. _( L3 V0 b- U# q% ~* J' d! H //初始化全局成员变量
6 D1 n- ?9 J" Z+ N6 \5 A
. t6 z# B# d- O* i4 G$ L* T this->bShowLantern=TRUE;
) S T( b% {0 {7 y4 Z
/ ~8 x# {2 e+ l3 a m_bf.BlendOp = AC_SRC_OVER;
8 u7 C. K3 y8 N" _2 `+ z0 f; K8 ]
m_bf.BlendFlags = 0;' |) X; J* s e3 l: S
! B% A. }! {; e* P- f) l1 S9 E m_bf.SourceConstantAlpha =10;
; x# p5 N- e5 Q; { o, \9 X' a. O& d: P: A* r3 ?
m_bf.AlphaFormat = 0;
- U; _. F; F* E5 ~% m( }# `4 \: E/ Q) `! u6 a5 U
//为节约篇幅,以下代码中略去对操作不成功的处理代码9 s6 f, l( t- Y: F: k0 T$ V. u4 I) K
4 v4 ~. Q' U' m$ [ if(!cross.LoadBitmap(IDB_CROSS))3 T+ Z% V0 k7 Q: C+ _8 X u
0 Q3 B5 O* x# s' f" B! [$ T f {
5 t1 r0 W6 }! k5 p% R& X% B! g3 e
AfxMessageBox("装载位图出错!");# S' d0 `+ U" _+ ?1 z
* c" U y4 ~% J return FALSE;
2 h' c& W: A6 M
9 n+ J3 X# a# b7 A, x! @$ u }
" n F, [2 }! d5 N
& `4 t. U/ L- x/ a9 U/ H! a; M/ g1 M cross.GetBitmap(&&bmp);) D8 M' i, T3 Q, ]
0 \' }" k$ L& Z' ]6 b lantern.LoadBitmap(IDB_LANTERN);
/ z% m# T) k. _ k& h( E+ ~( O. a8 y% N; k! f! h
cross.GetBitmap(&&bmp);
4 G) S, N* f$ w8 i( d# E( |3 t: u3 x3 t6 [. V
//获得位图的大小信息
$ r& L6 d$ I' c. B
: `3 `9 J7 a: I4 n( ]6 m) { bmpWidth=bmp.bmWidth; v0 v ~9 M: R% A
1 l2 P- N: P' K- e- h4 s" F+ `
bmpHeight=bmp.bmHeight;% S4 I2 s' ^& w% \6 L, M, Q
0 @9 @0 L4 P0 I. V1 Z
dc=this->GetDC();, g3 |2 n9 L6 t2 ?3 C' I' f; v* h0 P5 O" q
7 V1 R ?5 k) P* [ dcForCross.CreateCompatibleDC(dc);- r: q: l8 I; O
" C6 A5 G$ g8 i/ `+ X
dcForLantern.CreateCompatibleDC(dc);
: ~/ ?! r, z1 K$ P9 E, G) l! z$ N7 K/ Z4 g
//将位图装入设备环境句柄
& d* n! i3 M: K/ C$ o: y l: z, A3 [5 L5 c. c6 H6 A2 L
dcForCross.SelectObject(&&cross);0 }4 z6 J" t8 V: z
# [$ q4 m- i8 s, R. Z/ I dcForLantern.SelectObject(&&lantern);- n* e" v, Z7 I
5 m. P, U! `+ W8 F; c //打开计时器, c8 P+ V3 {8 B) G5 ]2 l+ z$ Q, F
9 {$ C" C) `# D* u3 I
SetTimer(1000,50,NULL);- T' F* R/ e0 k* ~5 ?
; i4 A6 Q2 s* `! c4 Z
return TRUE; 8 t; q( F: V; @* V) \
3 \) W9 \# f9 p8 k) M/ s }; q! q! k$ n; A9 G4 o; P0 B3 b
4 }; U- U5 A b% X* M, e* N
void CWipeImageDlg::OnTimer(UINT nIDEvent)
q$ f# r0 E, F I6 E- _* a3 o; H0 R! i4 Y+ G* F: Z8 e2 R
{
2 p6 O% W) \9 @. S8 V5 }
- T0 f2 B5 F7 [: T6 F* h //图片透明度每次递增5点+ |, B6 |( N3 L( {
! n" S2 n* K( j! y m_bf.SourceConstantAlpha+=5;/ I3 D8 h+ Z+ H# x
- Y6 t+ ?! Z5 L; ~ //当第一幅图片完全可见之后,显示另一张图片) @' x+ d& B& ~, T
5 `/ y, v' H+ ~/ r0 E if(m_bf.SourceConstantAlpha>=200)5 n: s- u6 ]3 T: F
. s' W3 _4 R- d9 m
{
# G+ _/ v( ?( ]3 k q
; w4 z% a/ W) m& J m_bf.SourceConstantAlpha=10;
* t6 i& k" q$ S" [7 X: r6 M" ^/ X# G+ r- A @
//将bShowLantern做为显示标志,确认应该显示哪一张图片 7 h8 ?- \1 y7 z& j
8 |2 @( u+ {( r2 t8 h: z) w
bShowLantern=!bShowLantern;( u# e* R, r$ S# P
* g0 q3 {% q+ Z
}8 o l" @, T; m, T# l9 r
' `$ N; R3 I0 f. L3 I1 H2 O) c if(bShowLantern)
& g7 ?; _) N. N- y7 f- `8 j3 }! p7 |1 d8 P# w
{2 t* t* U( U' Y9 c3 g
+ l# c5 W6 d$ ?" U: k
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);* `% f0 i1 D0 e2 C H, V5 y
( j# n8 S6 q' B4 @) A* m% q }+ |) L9 `/ G# ^# @6 J
4 s; m* u9 K" X6 {4 C. o t else! }" x4 n3 U& \
6 Q) |8 L# A1 Q) Q3 O- F
{# s! n5 f1 `2 C& C+ J5 j2 t
/ r5 S) }1 V! i. s
//按透明度递增的方式显示“背景”图片' D5 T, _( a9 c& c
: Q# E# e b' q' U5 F5 x% y
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
) ^: r ^1 v8 s: G+ A& G- }$ v
9 ~8 H: j0 ?- u7 l1 x0 O }
1 m2 t! b9 a0 m+ R0 I& W
$ w( E/ \3 K: ` CDialog::OnTimer(nIDEvent);
1 J: r# ]1 |# f% o" Q! `; ~ N$ x" X3 t# ?. V; G9 C- ?4 s
}
- t$ ~1 n, E( s# ^. e$ Q9 J
) N% A( D4 N5 S6 a1 U) K3 M$ n: ?& h' H3 ~3 M4 E4 H; v
编译说明
0 E! f) T) v2 a! S# j
1 y0 D6 g0 ?' z" m
; \' ~; R$ l6 p4 F4 F8 V 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|