|
周鸣扬 5 w& h8 L! u% X* Z2 x5 W$ b7 l
/ C G# a/ y9 g4 o% D
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
( y' F9 G8 r) r' {, Z4 Z, n. T/ l! v
/ t4 { s. T$ ?8 N' h: M$ q% I& f' ]' i1 Q$ I8 W$ ~
解决方案
' o, ~4 L. x: R8 K, \" R! t+ U) H$ Q8 k+ e) s
! |4 Y: b' m" G9 l! e" R: J
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:' k9 |! J* L2 m, j& H: r/ v
- ^( K! I% Z; y2 J+ |) o9 R
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 7 m, U8 C. ~7 k* Z+ Q( \% s3 b1 }
h- `/ Z# G1 s0 Q; N
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ; V0 ~+ ~* L& O9 A; Z u
* o$ m! y; g: T0 s' V Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
! P+ u/ n1 c/ B/ k" J) f8 y( j" h# V k; }. W8 S
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。+ ~+ g5 F7 @7 l/ ]$ O: m
, v' w5 B, W9 }% N% u. { 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:; D" T& p. z- ]7 H! Q2 L6 k, e
. q: A6 y& Y6 Y% N; |- z" G5 J
BOOL AlphaBlend(7 p* K$ `: C2 Z$ e" L8 C. L
; q) s6 c) K$ ^( ^4 Y; Q9 Y- n
HDC hdcDest, $ H% }9 l! C& F7 `) T0 P
8 A9 N- r2 a# w/ Y0 K) r3 B
// 目标设备环境句柄
# L2 a2 u: g+ r$ {, p" p( a; I5 \9 D
( n1 E1 }6 k, { int nXOriginDest, 0 o" R/ q8 J3 \; e6 b2 f6 }
6 W# N* `& B I$ X
// 目标坐标x/ F; C/ s4 j0 g
" h- ]' R7 X. g
int nYOriginDest,
$ ]: b q( b* V' ?7 G: K% B @: Z& v6 K$ i
// 目标坐标y
# {6 o/ W0 l& n7 r h# {! P) ?, G# v6 {' ]1 Q! s* S/ B. i# F
int nWidthDest,
) A3 y- }' @5 v$ u7 s' V6 i1 a6 o% r s; e: r
// 目标宽度
0 X( \- r- F) D; f5 M
3 S3 ?, h7 }/ ]1 g7 D. \( M# p/ \& S$ \ int nHeightDest,
0 `+ l8 m/ y! ^$ y* u* T/ i* m, f* l: X9 f( Q0 Z+ M% ] O
//目标高度
9 }( o. b; T+ q
0 h, V. D f8 {2 A/ G" C9 a9 f HDC hdcSrc, 1 X' P. t. _5 k2 S
) }- i# a1 ^! G //源设备环境句柄
+ n) m; c" X6 y
7 ^: X6 f n# C$ G0 f int nXOriginSrc,
2 d V4 S2 ^3 W `7 Y/ p: r* \5 g, k6 p3 P s1 l
// 源坐标x
5 Q0 z4 }% f2 Z1 K# s" |
( ]4 X2 r9 c' p6 O9 t int nYOriginSrc, 5 G0 `$ q) Y7 |4 ]7 i! a
' T( x- c* r& S j* Z, X$ T! X
// 源坐标y
8 N% X! v: l! |, X: z4 R' B4 \) ]7 ?& U# n; T( c6 S
int nWidthSrc,
+ R4 \4 F/ [) T, b
5 ?. h4 r1 y |) l% Q. ]! f //源宽度+ ]( H. q- |( G+ w) |
' U l3 \: {0 @$ K$ i2 C! c' g
int nHeightSrc, ) a, I- y p3 Y/ ~3 ?4 d+ k
8 X, U; {) O, s; A //源高度
' `* t; u. B1 G) K9 o% g. X' w9 h; {* K: V" j. V
BLENDFUNCTION blendFunction * c2 [6 Y& u1 \
; @' | m3 B& Q4 v- R9 m- k
// 合成方式具体数据结构
8 b: P4 [: l, X+ j1 u( a! X& P3 Z# U Q7 ~! S5 z. e
);
( I8 l' ]% b) c/ R4 _! ^" f. V2 T' R) o
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
; v% U% v9 M h% ~- |1 {
3 H2 `9 x7 C0 [ S1 \, P% w. o, ~ typedef struct _BLENDFUNCTION {
7 b2 g2 b: p. p0 \- W
! a1 u% }7 h0 Z5 g) W% A+ B BYTE BlendOp;
( }5 P8 b0 y5 M1 t) M# v0 j4 a6 J0 z8 B5 }+ F: C% O
BYTE BlendFlags;//必须为零
. H( H& ^; b [1 C1 h) u" r. p- } @0 u) m' t! ]. @, j8 d
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
) f% W2 q" }+ @+ K! V( v' ?5 V0 T8 `4 Y0 Y0 C+ M
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA# `, X* f9 v; w0 E% w A# I y
$ U+ y3 c) c8 J4 k7 @1 i6 Q5 J: W# h) j }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
4 Z& u6 y/ m0 t) L7 |9 [! a; E) s4 K: O
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。- w0 F/ r. V- I2 j- m
: l ?) l9 ~5 j- F4 u
; H& `/ ~; C0 f# L2 b$ x 编程实现
" {" r6 B3 F5 Y( _9 z$ E8 D" Y, N. M& c) p
7 F+ q/ z: Z n
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
7 \& ^# j1 ^( X9 [
/ x. S2 d' m( K 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:6 Z0 C2 X. k; q/ r: l& t' a" M
$ i. K# y; {% p6 D7 x2 U
class CWipeImageDlg : public CDialog
6 k5 E% b2 [0 f' E3 Z% U: r2 j2 R7 `0 E/ L. q
{
1 U0 ]5 U5 I) |+ d5 Z+ F. x* i" c9 B9 F4 U$ S. `
// Construction
7 i0 P! x8 A @9 ^" b4 `* _
# m6 O9 R1 M: T1 O public: B, C, I! A" I+ r" R" u
7 t; Z9 N5 H* @! |, e
BLENDFUNCTION m_bf;
& z8 R; {4 |( |) ?: t' S! G% _' O. q& I5 n
CBitmap cross,lantern;
$ Z3 ]: y6 Y5 m" H% U( K
! N9 y8 k. s$ Y z5 H' o BITMAP bmp;
# Y; u" q% w9 M
: J( U# f* n3 X' _8 Y) x- z% V8 k6 {! s int bmpWidth,bmpHeight;( v+ i, t, @7 _) b
3 O! F2 R: T3 C" U U' W1 v6 e0 A
CDC dcForCross,dcForLantern;! @, S) P, v; n$ p: j
# Z) d6 w7 G2 I% K CDC? dc;$ A! a; M, [8 x' \% V
r* I+ S2 T' m, n0 H BOOL bShowLantern;
% ]1 I$ w, W$ D, w; N3 r; Z3 t. S7 e5 C
………(系统自动生成部分)
) T6 q3 n$ F* B6 P7 K! P9 L5 M) @ h) n- ]+ b- W
}; 7 e, G7 U% L. U# e) z0 m& D# U( |8 o
4 }0 b( Q& w9 H+ n 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:- t$ h% c, N- V* G
8 A3 t9 V4 x8 v BOOL CWipeImageDlg::OnInitDialog()" t! C5 Q- k! M1 h
( |5 q: P, W. j+ ^" {
{
" Y/ [+ g* y! `4 L4 ]1 Z3 y9 v0 E
/ p7 k, V. X0 |7 Q4 g" h ………(系统自动生成部分)& k/ Z" k b2 [5 l, @3 y, \
2 r4 V5 }/ i% W5 `9 O // TODO: Add extra initialization here* A8 e g1 \7 d0 _3 ^, y
6 ]3 d5 t8 T0 c: ~% P# w: v0 X# _7 p //初始化全局成员变量: v! m: M& h: ^: g7 q
9 t( P7 d) v) e$ A( ?
this->bShowLantern=TRUE;% ]- z: G5 r6 V
8 v/ _, h7 T. }) }
m_bf.BlendOp = AC_SRC_OVER;
9 K7 j6 A! V* t/ A, s- W0 f& ]- N6 |) F
m_bf.BlendFlags = 0; E, B( J B9 `3 i/ s& T1 v
' m! x; g2 e- i6 Y" i) k: w m_bf.SourceConstantAlpha =10;
3 X) X4 a; b) e. Q i
' _* o; S6 c% z1 [ m_bf.AlphaFormat = 0;4 u9 c) v3 f/ b- |
; U; H% G! `2 I4 D1 {6 Y' v
//为节约篇幅,以下代码中略去对操作不成功的处理代码
/ L# ^# K7 q" o
N: E7 ^+ ]+ v& Z if(!cross.LoadBitmap(IDB_CROSS))0 ^. q$ X+ w5 _- n7 O$ J0 S6 p
) E0 k+ Z1 X* P
{
' ~; u! j% Z3 ]# P9 G* r6 n0 f X( K0 y; ~1 h1 E5 K
AfxMessageBox("装载位图出错!");
& z" P* K; d! X! Y! S3 f; F6 E* g* m6 @. `" J/ y, c& y
return FALSE;/ X4 r" y- q/ L7 s, Y
: R. {! n0 L# n$ B, l% P
}0 M$ @" i; t0 n1 D/ ~
; e( z7 k! x. i1 o) d. x
cross.GetBitmap(&&bmp);
! x! e8 M& Q+ q& b0 e- k
' g2 ?0 G4 D* Q7 O3 P: I lantern.LoadBitmap(IDB_LANTERN);
1 z$ t8 K* n+ o+ c/ g5 d0 G2 n7 F+ F- {& B6 c
cross.GetBitmap(&&bmp);
" }7 u! Z$ w( Q; v% z/ W
- U% {- h8 Q+ X. U9 j //获得位图的大小信息
. J- [* \; \6 l) P# V
5 ~# H, | y4 F# F bmpWidth=bmp.bmWidth;$ F; n2 O. y) d
8 _7 ^1 `+ C1 N
bmpHeight=bmp.bmHeight;7 Z0 c. K: Y% i2 [( b9 [0 {; Y
! M, K3 N, E. \- \- e$ p dc=this->GetDC();& |5 `& z2 A1 D. C6 c
3 S4 t, t2 s n ^3 A dcForCross.CreateCompatibleDC(dc);' P" G( C# b S& J" U f" {
; `4 q8 `% s3 \5 _& m* }& e dcForLantern.CreateCompatibleDC(dc);
( ?- s4 R* V. t, l8 B$ S% p8 T
$ a& z+ U" d z8 [% B //将位图装入设备环境句柄9 y/ u5 J1 G) `8 C( W2 Q
5 ~/ Q- K/ i$ ?( k dcForCross.SelectObject(&&cross);( D4 {+ U; D5 ?8 W# m+ R( J8 _4 _9 G
, C, K* c r3 P, e% g) X# a$ ~8 ? dcForLantern.SelectObject(&&lantern);
2 A& ?8 H8 l# n5 ^+ a% n* s
* F6 ^, Z% Y2 M* _' U S- } //打开计时器
1 L3 G9 D7 B" ?2 \* h8 ?7 Z {& n$ u* }( W
SetTimer(1000,50,NULL);& O3 s: C3 `2 l' @, R
, ]' Z4 M3 v# m+ V* z! Y
return TRUE; ' h9 R3 a( K' ~/ X
/ `8 x4 N0 ], k" J0 T- L: o, Z" l
}
8 T( S$ I: l) s9 x8 |3 @5 K' j) }+ x) n ^% |; K
void CWipeImageDlg::OnTimer(UINT nIDEvent)
: Z5 }$ z2 i* E. i3 b
8 o7 O9 `# Z6 _& U {
2 j6 {. ~" q* r# O# T5 S- ?" r A! @+ o& ~! x: W+ U
//图片透明度每次递增5点
( H1 d/ g$ b) N J( y/ C3 ^; f- ^- H% t, P1 O e' o4 ~
m_bf.SourceConstantAlpha+=5;" P# [; P2 P# _. }4 d& E0 M
: @ a/ o: s1 c- T
//当第一幅图片完全可见之后,显示另一张图片6 L$ A+ c8 R- ?% b* D/ o4 [5 |
: s& v1 p) k1 T* p' B/ R4 v
if(m_bf.SourceConstantAlpha>=200)# U- w3 w& d A/ ~& c
; x/ Y" j4 V) |0 D3 g- u
{ |) d7 d% D d1 _6 w! w
, X6 X' O( T$ H6 v, l: e) P! Y! f m_bf.SourceConstantAlpha=10;
, X/ c) i9 P8 o- A9 L% K* d* g
$ b* ~' `$ I- x$ q+ x, m //将bShowLantern做为显示标志,确认应该显示哪一张图片
' }; d: K$ o) ^1 Q# d! U$ A- U
- y% I7 m' {% _8 X1 a7 L( K6 b% y0 ?" s bShowLantern=!bShowLantern;
' n [- f! [4 [* q. K5 {
4 I; |; q4 R# s7 }1 m: c }7 A2 C6 G% O- P" J7 s
8 `2 H3 u9 X% x% J
if(bShowLantern)
9 O3 [* m4 a: J5 G* c' E2 w; f
4 W8 m+ d; G$ t7 N" a t {( p# o) B6 i8 T9 h3 k9 B
- T0 o) ?8 t" E
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);6 _2 D: m2 C- d; \. h
* x9 ?3 E: B& v' ~8 t }
7 Y. ]% x3 {* Z" C
4 o+ P, E$ I! {* c' n6 S+ p else
4 B" A1 r+ B1 {3 \' Q. @) A' B; }9 A1 Y, ]/ k7 \( R+ J
{
; H4 E$ }5 N0 k
! \, Q# F: V' ^- g //按透明度递增的方式显示“背景”图片
+ [ p3 H6 m) Q* z# t9 R
& B7 k# j4 R, R* M0 U! e AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
$ u4 i6 V+ ?4 r1 B
+ n! W/ }- O1 l* }% s8 J* _ }
& e; a6 W' ?$ ^' C5 I( q, I- b
. p( C% s y* {$ U6 G CDialog::OnTimer(nIDEvent);/ K! x0 u( n- `
: J. b/ [# |( e/ h: L1 W( b$ }+ f' h
}9 u8 u& U* ?+ D0 y& Z- f
6 X" X0 Z. M+ v( N( G5 \( ?9 [& T9 L
, c& v# }8 J$ h! u 编译说明, n$ F; y# c* k9 j5 S. R
! h1 f: I$ K$ M$ _1 ^3 {
' z2 V2 L* W8 F* x# D8 a6 t
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|