|
周鸣扬
. l& e* e. B- x' s$ u1 d7 [+ Y$ ]0 U, E: D( G
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?7 q6 s4 o5 _7 e- u
; p# O: e; B) i% F- o$ u# y: |# t3 ~5 K" \
解决方案
: i& d0 S( v7 E7 \1 J! O3 W1 k# E% p; I, j
3 v. f3 B$ d3 C6 o' R
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:3 {1 [8 o8 J% v& F9 ^) F( x
3 H- Q0 `1 K( N4 V. [" o6 o2 c0 z
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 4 @6 }8 R7 \2 v( b' @3 l
* d6 P: r! F8 e5 x! E B+ ~+ x
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ! P$ K8 h7 ?: W8 b. k9 M" f; [
7 v, b1 e. I0 h6 T- h$ J( j( ` Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue% ?3 \ J3 {* w& A2 L+ _- ?- |
* ~% s9 m3 m; g* ^9 _ 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。 G. ~) ] O" o6 E" b/ m
7 p ?9 Q% D7 e1 B/ k/ B
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
0 v i, S8 ~8 N$ Y% M4 w5 ~( f
* B* B- ~% [% V, \! v6 Z BOOL AlphaBlend(
# `! N3 |5 Q A6 P. t3 ~0 t7 R+ W2 m; C& D7 h+ h7 K
HDC hdcDest, + r" |" b* q- }8 e
8 E y( [& R0 ~- W, x# V- z& l // 目标设备环境句柄
" o& B9 S' E9 q' B. \- V9 F, @
! v. I7 @" X, r8 v int nXOriginDest, ; m5 ]( i# H( g% D, c; e( P
+ l {" u1 o2 J3 Z% g+ p, S# x
// 目标坐标x' Z6 A( O' v) U. L0 s
: G) o/ L& J/ k
int nYOriginDest, & I1 m' t, F, }' Z) e$ `. Q1 _
) ?- d! T6 s4 r6 z3 z- g& c* l // 目标坐标y
7 t6 r1 e! ~, c" z8 p, d
- o+ E9 f. K+ N& m! p, t6 F! x int nWidthDest, & m7 U. s( Z+ B$ k% S. O# J8 N
' d N1 t% x% q/ `* M5 s
// 目标宽度7 F2 ?" e8 R! Y* O0 M+ Q
! s4 [3 r; t3 q2 N/ P) f8 o int nHeightDest, - E6 h+ T% `9 y* G
# h: ]% _; t" O- n
//目标高度1 @& T, {- B' }. s3 `2 t
, N1 A" e; k/ p$ [ HDC hdcSrc,
1 Q5 N, Z6 B; ^0 s7 _ e. \% {& R( |3 v" |0 L0 H
//源设备环境句柄+ \) X' v/ E, [- M! L
1 [, Y1 T" ?! L8 l' l* u h int nXOriginSrc,
) v5 b: L0 J$ d$ P: c' B* `; u* Y
& B/ q( x/ o6 S' H+ I0 V // 源坐标x
/ j+ @' }% P% f" Z* m) r9 c Z$ B( w/ d9 H
int nYOriginSrc, , b% ?: H/ ^7 C3 e( O
/ T2 y3 b* n: N // 源坐标y
1 |* D. P4 Q; c& c5 p9 |& l! w3 {- n e- W9 B _1 m
int nWidthSrc, 2 z0 Q7 G; [- B' ?; ]% E
7 H& Z7 b6 e8 U. ~! F% d; N //源宽度& N' j2 U& {- U! L' c7 j/ P( |0 X
# K' b5 l4 }6 }3 D/ _4 a) ~ int nHeightSrc,
, i b! y# A& K: V
2 s6 B: S; h, m2 M( p- _" ]1 m //源高度
% a0 _( I6 q$ E1 O1 k4 \" C$ D. |
. ]& L4 `, z4 q BLENDFUNCTION blendFunction
; o5 P0 H' p W- M2 M* n% \7 o; i% H- k' ?2 ^
// 合成方式具体数据结构
/ }: L' X7 m- y" a% R, U1 }9 m
% z+ _. f7 a% r1 L- N2 d+ E" l );
, q9 p0 M* \. S: V! Q0 k# f$ E8 ?4 s2 t( m" `' g% a3 m
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
/ A; L- U8 x \ r' N* J* k" ~6 q
typedef struct _BLENDFUNCTION {8 n; I0 f' Y" {1 c0 |" D7 R
! R3 N9 i; l1 n5 G) Z# G6 S! {. Y
BYTE BlendOp;
) J) c+ g' x5 }! |1 z& X* x! K) y# t
BYTE BlendFlags;//必须为零
+ |5 c! j- u' q! B4 u% k! h B; s
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
# }( n7 G! I, ]
& A5 l9 r- j( O/ M" X BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA0 J/ Y/ o& X1 t" Z( k3 e
1 P) r' t0 N( V# T+ s, F }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
, K" p1 y9 [) L/ p: t- m5 r% o, o# w( E5 g
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。9 u7 [5 D% J. s9 w
4 d F% ?7 I) j* z" |* D
5 g3 \! S1 @5 o. B2 B5 D
编程实现# d- n+ w! u& t6 \: k. V
6 R2 y- P) d8 K& Y7 m1 B! e8 y' z2 J% i- a8 a% N# u# ?
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。7 n! V5 R( T8 h6 f. m2 b8 s
6 ]5 b0 w+ p; X! x7 h 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
. L, Y1 D$ ?2 R8 { I' j7 K, x& [/ R S3 X5 T: ~( r9 d( k: o4 t) V
class CWipeImageDlg : public CDialog
! r9 q7 N+ r# N: R! M% h* I2 {/ I# f) L, R3 N. y
{
: J& v6 M0 i# a
# L C P% N) k6 ]4 F: M) o // Construction* J) j' U5 w! r2 [( E
v( m6 u- a! M public:& h) _9 E E, ^9 f" V
c0 F0 ]9 P* q; M BLENDFUNCTION m_bf;7 i2 C3 ^* v, Q2 p/ S' P# q
" B5 P# b% s/ `: K
CBitmap cross,lantern;
2 x, q1 |; } D/ R+ p4 I3 H$ ^" i- B: d6 O: i: _% Q I
BITMAP bmp;+ f- G- M& |8 E* Q0 S* l8 T
6 D& P+ U4 _* X+ l5 e! q8 G int bmpWidth,bmpHeight;
$ b6 _9 s6 Y+ J8 ^3 Z: ]- c( _- V- s% ?3 B
CDC dcForCross,dcForLantern;
9 r9 w8 k% n; v9 |+ _: F5 C8 k @$ M6 }' m0 l4 }" l" x% y0 f' V8 k
CDC? dc;
# F( s- l" o4 [9 H, Q! X. N+ G) P2 c
BOOL bShowLantern;+ x; E6 Q4 L: a/ I# G$ s3 _7 L1 w$ b
7 \( A$ {! `+ T* T3 T
………(系统自动生成部分)
7 k& [. l( \ Q: G/ D* L5 _
9 Q$ z/ g& r% t* s }; + `: {7 t7 R5 C2 ~2 C9 O- n
) ^# O, r. n# k- n 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
" W6 C3 r, u3 M2 o' Y" w; h
% F! Z; c' a4 ]/ m( O BOOL CWipeImageDlg::OnInitDialog(): [. U* A2 M/ G7 `6 q. ^
- x! A3 S9 t* _9 R
{
; Z s0 h) w6 T; N8 Z6 f9 G! |, K" v1 A& s8 ~/ h
………(系统自动生成部分)
7 d& P7 P, C/ }+ e& e( c: t7 b/ U F1 p* O7 a% q
// TODO: Add extra initialization here A/ H, a& M( {3 k
" g+ ?& h2 g2 E; Z //初始化全局成员变量
' R$ O. }& _9 W. Y3 `$ n' h8 W: D0 F# n2 n/ U
this->bShowLantern=TRUE;
) g; b! ~( j0 i- |. i2 `% Y' d% E; ~5 B6 v# }6 m
m_bf.BlendOp = AC_SRC_OVER;
8 b# x9 v( @& M8 L* W% x5 Z+ b# e0 F% @
m_bf.BlendFlags = 0;6 X. e7 \5 I" O- z T9 u9 P
0 R, Z& x j, \4 y( r5 o1 s7 n
m_bf.SourceConstantAlpha =10;
, Y. V* Q3 V1 p) C
: }% r, s- r2 h1 c( {; j3 E* a m_bf.AlphaFormat = 0;6 u8 c2 x: @* V) K# w4 \+ b
) N5 B* h( [& t% q6 {3 }
//为节约篇幅,以下代码中略去对操作不成功的处理代码
% q8 i! g) ^7 ]: @2 T# \! v7 h% O7 C
if(!cross.LoadBitmap(IDB_CROSS))9 q3 C g+ w8 [+ S
) }2 h0 q% B n% F N7 \
{1 Y9 [2 \% z* i. S) ^9 n( x1 c! V
# B h Q* H* |5 ^, | AfxMessageBox("装载位图出错!");
3 D$ i( X4 u- _0 ], d# W/ `: d: i# D
return FALSE;; [: q- i, U$ W* Q' X6 Y: P' t L
% x' T/ n) z: R) f& }
}
' f" j4 F! q/ j) E' u* P9 ` i0 L4 a9 u
5 `7 y) ]" G! c4 E cross.GetBitmap(&&bmp);; @# I; x7 d! K5 ~/ k/ S2 a, @
% p+ q7 I7 k; K0 q lantern.LoadBitmap(IDB_LANTERN);5 ]" D' a- q6 d
$ M0 n4 [& g1 _+ A% L1 M$ }& z& Q cross.GetBitmap(&&bmp);
! q$ B* I- ~9 {1 U4 _8 E+ Q2 f
; S5 R: V' P( {+ X* k //获得位图的大小信息' h( X7 u- [4 U& ?0 I `" V
% |' F% i* [: l7 S
bmpWidth=bmp.bmWidth;# c ?7 h& D. _2 Z4 m+ d4 @6 E
" z8 q' c0 u1 b& V- M
bmpHeight=bmp.bmHeight;
5 m* S$ A; T" c: B# g" p- o1 k& _* L1 x
dc=this->GetDC();! W2 Z* w; \* K
" j" F( L( F/ _' @ dcForCross.CreateCompatibleDC(dc);
f$ y% `4 s, M( C) ]
8 N" m$ R+ C1 F2 f' v' @) o; i dcForLantern.CreateCompatibleDC(dc);
; f1 d* N" J; K
8 L/ L$ b3 ]( B+ ^. B' o //将位图装入设备环境句柄( ~- g; T9 ~+ Y1 t- N
" k1 D7 y" y( H8 x
dcForCross.SelectObject(&&cross);- a6 ]( d0 p S8 K# Y/ U
9 u6 ]* L1 m8 P% T$ V: C dcForLantern.SelectObject(&&lantern);8 B2 l" A# [9 g0 y/ |' n: I
) W/ t" |9 _9 c3 ^; k2 I //打开计时器/ @* o+ J7 H1 \' k. j. G
5 d8 F1 Y0 B( K SetTimer(1000,50,NULL);
- i5 {) g ^! k6 H, I) T" O8 c& L( e+ n3 i
return TRUE;
9 ]0 c9 {/ P3 ^, w& E: e
% z2 c( c$ D! } ]9 G4 N6 Y$ A( H5 I }/ W. R: F& K& Z; e9 o8 G
% B' O4 m' m4 _$ V# R$ E8 x% S: U void CWipeImageDlg::OnTimer(UINT nIDEvent); W+ K3 s6 `# Q: R' G
* j2 Y: r& T8 C
{
/ @# S1 x" ?* ]* P. ]
$ m9 O9 h e8 p. h6 m //图片透明度每次递增5点
0 R! v& G* k8 i" l5 j1 c8 e5 V. n; O9 S; N
m_bf.SourceConstantAlpha+=5;
( B7 G g0 w; ?- ^/ @7 z! Q2 _" u& D5 b) X3 p t
//当第一幅图片完全可见之后,显示另一张图片
* F1 W9 N# l+ X( ^/ N, `! Q% \
& D; j7 l u8 o! Q if(m_bf.SourceConstantAlpha>=200)1 J: k6 N0 `& T, v( [- K4 J
8 m) q4 }) |) c1 q8 c- a {6 g0 B5 B3 N/ l
: k3 k/ B0 E0 @ B8 W' k( O
m_bf.SourceConstantAlpha=10;
5 r' z/ t# o5 Q
! y! \1 U+ i$ \ //将bShowLantern做为显示标志,确认应该显示哪一张图片
& ~$ y) w) u2 r4 A5 R
. q) O) k3 a1 y bShowLantern=!bShowLantern;
. q% C& V! C" C/ i, G2 T" b% i7 w: \9 \
* L/ F! q# s' x8 V9 j" ?6 l9 h }
% r _; v4 i! d% L
- a1 K0 Y6 T1 m* r if(bShowLantern)
3 b1 ^$ \) o) F* F: S- p! e* ~
{2 c& Y2 A; e; Z1 y/ ?7 S
& l9 A& k( D. E6 g
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);7 q6 L1 K1 c4 S% T; x* ~4 D
9 q3 [1 C1 t0 g) @/ i; u) D }
% s6 o& e' q& o% Y3 D) u! c; ], I. h& L' Y
else
. q% L7 r- d! G5 q/ \8 `3 R. F" D2 I+ E/ H8 Q. z5 {
{
2 u6 T7 w% f) U
! M: R' h5 N1 x i9 J //按透明度递增的方式显示“背景”图片
: w6 E' H5 |1 ]) M1 q# Q. u
5 _) V, U9 e/ ^+ ?% D) f: Q: e AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
$ @/ g' P5 ^/ n$ R& G/ s6 R" N) p; u6 N" N
}
6 ^" o- W& w: Q: f7 |7 X% y# R6 ]% c: d" Q7 H
CDialog::OnTimer(nIDEvent);& w/ k; ~+ y/ S/ ^1 Z7 a( k1 C
" W2 }( s0 J5 P3 z9 f }
5 v2 G5 y6 Q; S2 K
# a6 c W5 ~1 z" R) p
: {& A3 ~3 [1 s: [# @# ? 编译说明
8 }; j2 s8 V2 S+ m& O7 ]: J
% k; g& N- q$ k+ q* t2 N
% [9 V( ~" V2 f" J% ^. \ 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|