|
|
周鸣扬
6 Y% J$ H, m/ |% b p0 v
3 A( \" ]3 ]% V G 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?6 y7 q5 B0 I! a& e
$ F. C7 V% l0 X* F K
( A I/ `4 b$ \; S/ ~$ S
解决方案% f$ ~* {6 j6 q! ?
/ V4 W2 x4 }# f0 U
: K4 t ~5 ~" b3 x9 c# x 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:! t* J4 V3 G9 `) \
( S% D2 }$ M6 l: B- X+ F: ]1 M Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
* B% i' G, H: T: I6 T( l* i4 ?7 V$ {
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ' P0 P$ b- T$ y* ]( j. [. ~0 q
7 y' J. ^. _- O. k
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
5 r3 \( B( O0 Y$ ]$ ^: \' c; p7 Q: V8 \. F
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
4 C! ]. j2 t U5 y. ?1 C$ C4 O2 `
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:2 ^1 ]) m+ M0 Z. d! ?: U
: S: j( m9 x' M. E6 q6 B4 z
BOOL AlphaBlend($ a& Q9 j1 h1 t5 W" `
4 q; y9 o6 q3 k3 A" [
HDC hdcDest,
: j9 ^- y3 D1 u8 N' c! ^* C
0 t! E4 e" {2 q; i: X // 目标设备环境句柄/ [' I/ l7 a, a
0 G( N% V+ c" ]5 x1 c int nXOriginDest,
3 l. h" X+ U+ Z9 y3 A7 m7 D) o4 b+ f0 j$ `4 Y: n0 X9 V: D7 J
// 目标坐标x2 M% }8 q9 ]6 }) k& h
: Q* Q2 C. K* j) m/ g/ E
int nYOriginDest,
6 l4 Q( h9 G2 G/ R; Q+ X) u
3 o7 f+ J% X9 O+ O) `2 Q7 `9 J // 目标坐标y& k( T3 I, H; b2 p+ a1 Q# V: u4 h
0 `, i; J2 c+ a- E9 l& |2 a: P7 v, F7 X int nWidthDest, : ]( b/ |! @1 W) ^# d9 K
2 d* j7 D/ j# ?5 g, P2 C0 y5 {
// 目标宽度0 c) l( _9 u( D
. \ _( E8 T* v
int nHeightDest, . ]7 V4 u5 _" U( y1 m- f
9 K, F5 I, }, d3 n6 ?* O- e( } //目标高度
8 D, s) q: V7 K; n1 a' X9 m% Z ~" ]2 J2 E1 M4 N
HDC hdcSrc, 7 y% _1 _0 Z, E
2 u9 h. {, @: i2 m! ?
//源设备环境句柄' i6 r! p" d1 y
: y4 g" O$ C) L: k, z$ j
int nXOriginSrc,
( a, P; V8 b! _6 \7 h
) P% ^, o8 m- X# m. a // 源坐标x) s7 ~( l0 S, Y* l
" l9 R) @; O) O ^ int nYOriginSrc, / P8 J4 v9 \! L5 f
" F. i5 T( N: G // 源坐标y
* k, p4 M) q5 I% s
: g7 z; V; Y& W9 Y/ _ int nWidthSrc,
; a: _1 Z5 T: U! X; p3 w( g @% Y
8 X$ u& r$ P* s7 G' c2 F q( l //源宽度
1 @7 j( {4 M7 s" a" n5 O! ?
8 {6 \: \# w+ ` int nHeightSrc, " k% i$ o! M1 ?; p" e$ X
5 u1 g& J4 j' A6 D
//源高度/ l5 \7 p( Q0 j4 f. Y' P7 k& ?
; E9 n; r9 ]4 o" m" @ H BLENDFUNCTION blendFunction
# u4 A' S8 ?" N4 y) Z( F& T
7 e- e" v% i- {7 P9 k1 s. T( ~6 |- \ // 合成方式具体数据结构
- \3 k6 o- v/ R' r4 J* J+ d
+ A1 Z) f$ B2 i: x );
8 ]# J9 e, J- N8 c: x, {1 @5 X$ R% W- ~ E+ k( |* L! l4 i% [
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
8 H; y' n" q8 i5 r& s/ U. f; u0 t( W! w+ n2 \5 D
typedef struct _BLENDFUNCTION {
9 P" B( t9 f/ e* R: U8 y% e
/ R" M% `( Y' e5 @" d" `; F3 f BYTE BlendOp;
% T9 |/ ^( Q) D) w& P8 O" Q B. P/ |0 z8 i; w1 r
BYTE BlendFlags;//必须为零: W) [, w8 U: J$ u( c* M
/ n+ z$ l# e" Y: X" V4 O BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
1 [6 x, e7 A! X3 Y' s5 T9 _- h, ^( l; }5 S# ^7 N2 B
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
" b, }. Z' l8 Z d; m
. X3 O7 e1 M8 H4 x' u- T& k }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;2 d: Z5 H! g# F, O, e6 ^
) D B3 _ O- e! \4 y. r 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
7 S9 \4 X$ a9 r# u1 o
5 f; n+ c+ S! M
0 G5 [6 k* p2 z" g* ]+ X 编程实现
( V# d5 s: @' T- \1 P
6 l' D0 T4 t5 h) K+ S& |* X3 \
& a/ \* j( L/ `# y 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
( v8 n* C- h$ @/ O$ N. n$ J; \
2 T8 |. ^+ ~6 E7 N7 h 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:. B! M* ?& a3 a/ U$ e( O8 j
% l' \5 [9 J% M, Y. R class CWipeImageDlg : public CDialog" Y1 t) Q& f: E) b2 E- ?; A
' p4 C( f1 s% ?6 s n9 R8 e8 H
{! f. I; }. Z1 K
9 `) s6 ~& x$ y/ Q) o" E // Construction9 ^# i j. ? E5 j2 A3 {* K5 h' f5 O
; i3 G: Q9 r; B) [7 d+ z
public:
) P! G: G6 }: w7 }. k& e. Y# H6 M
3 e+ L7 ~+ j1 _1 N4 x+ p BLENDFUNCTION m_bf;
0 \2 X6 s* P! z- C3 m5 h) h- l. Y' c( [3 E7 O, O
CBitmap cross,lantern;
h/ \" ?0 l# J
$ t3 q" o' G) `% U BITMAP bmp;7 Q& Y; A" m: i# [7 o
0 u \6 }% M6 k int bmpWidth,bmpHeight;
! Y* Q$ T+ U: U6 d1 [, V
& f% m- S$ ?/ ~9 F# Y CDC dcForCross,dcForLantern;& R# ?% k" N0 t) ^" e0 Y7 g3 C: ~
! H$ G, N, \6 q7 G0 D) |# o" N3 ]
CDC? dc;6 v1 C- `6 P2 n: `! L. C' Z8 U
0 v, D! e" p# C7 S
BOOL bShowLantern;* [" `& o: N# f
$ s* v! `' ?- D. F1 Z5 S7 K
………(系统自动生成部分)
. r- f7 C2 V6 f7 ]. r
, ?6 ?8 k7 @ X: x8 l };
. T, G0 [; k2 s: `+ e6 s. G- q' e% J& L% U4 L
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
\; l' b/ ]/ ^9 }
) Y% l% g- M# }" k+ Y1 x! u$ _ BOOL CWipeImageDlg::OnInitDialog()
$ `$ y Q, w: ~: G! |; |2 ^3 v8 G) {$ }
{
, t) ~% h2 t- z0 x: h2 L( f6 A/ ?4 L4 P- b- K
………(系统自动生成部分)6 Q# U! c8 x* w- R3 Z7 U. t* m
: Q. Q# @6 A: S, _$ m
// TODO: Add extra initialization here
9 b0 B V! T- M) s( z* ?" P) B! y9 K+ W) S" p. H. d
//初始化全局成员变量
0 `) e/ X6 [9 T' i$ x! F* r( v( P! r+ V; B) t2 J
this->bShowLantern=TRUE;
t8 i9 j* U2 }4 O5 w2 e
0 X T( K* Q) H- c1 ~; _& T8 f m_bf.BlendOp = AC_SRC_OVER;
% z& D) J7 S( O l5 ] l6 a* h1 S; A* d k
m_bf.BlendFlags = 0;
4 I. r7 d d/ [
7 B8 r+ K" ~7 l& Q3 i: X m_bf.SourceConstantAlpha =10;" L/ q* N# n3 H( X( K
' J, R; `' R. e5 X# j# v m_bf.AlphaFormat = 0;
/ x9 X8 r# ^- r) h6 C, N+ j! a+ w3 n/ d# D- s( K1 F, z( K
//为节约篇幅,以下代码中略去对操作不成功的处理代码& w& A1 O# [! @& A _8 x9 E+ o5 }
2 p( J1 S+ p8 U7 P; R
if(!cross.LoadBitmap(IDB_CROSS))
. \- j' I, L2 _2 K
9 e$ k* d/ ]- ^$ U' V {" F9 Y% J- D- H! Z) a; y
/ a& B+ T2 t- z' f
AfxMessageBox("装载位图出错!");
; }5 n# X2 Y. ^. c" B& x$ m g% ?6 B
return FALSE;7 b6 T( n3 ?" ^# ~ Q- b
( B2 {! ]/ G/ {
}# V4 U/ p. o5 i6 L& _; _9 W* i
. O% O! m+ Q6 K3 o# K% y9 x
cross.GetBitmap(&&bmp);
3 ?) P- y! j5 S6 z# R3 P
" D8 A% k$ v/ d+ a. N1 u1 @ lantern.LoadBitmap(IDB_LANTERN);
6 X, L, e, p" X* H! d7 w9 U/ e) l2 N7 w5 B, {
cross.GetBitmap(&&bmp);
# p* T9 u' m! L) ^6 F
9 n) G- i$ V) h6 F; Y! H //获得位图的大小信息; ^$ Y K, J0 Q! b& V; |
4 S" T# j5 ~+ R1 ^ ^7 I* }4 Z% V bmpWidth=bmp.bmWidth;7 z& R6 ~: r# g0 h7 k
$ k0 a5 D" ~. }, Y: E& z7 j: L, C bmpHeight=bmp.bmHeight;% t6 R) Z5 r; ~2 B/ U
% b) @3 O* `( `/ ?- \. t* k& c dc=this->GetDC();2 ?# i9 k- V/ g5 Z( p- q& g, m
8 n- G6 ^; D( F0 ~- k- C
dcForCross.CreateCompatibleDC(dc); A' ?" J2 H/ D Z7 m3 E
! ^. J5 p' @+ x6 M
dcForLantern.CreateCompatibleDC(dc);" A+ x& _% C' A. L* s2 s
/ ^! i7 N0 C: v$ l* `
//将位图装入设备环境句柄
, C4 I( @. @' E2 a& Y q, }, Q; |4 A/ U7 q
dcForCross.SelectObject(&&cross);
/ {3 ]( R* A! a) k/ F& m# s4 h1 u# r# U' U' E U
dcForLantern.SelectObject(&&lantern);
; i3 V9 x3 H2 I0 P* t# e: ~' b4 u9 Y7 A& b' L# J6 j
//打开计时器
/ o4 t+ U( g e5 Z, b0 w8 b
- B/ }+ e/ B# {- u0 u$ y# A SetTimer(1000,50,NULL);
+ ~2 F S* G. d) G7 q/ E4 b; w% V0 J2 L& ?5 J" r
return TRUE; - [& R/ d4 _$ ?6 a4 r. h8 \
4 t8 Q$ b Z. h% f
}1 f, I( [- S# Z+ Y" G+ }3 l: r- M, z( U
z, G0 V$ c: r* g) A8 `6 Q void CWipeImageDlg::OnTimer(UINT nIDEvent)
4 B( O5 S' Z; p5 s3 w* s8 T/ ~0 u) Q+ E# W
{
- F ]8 t* g v/ |* V0 N H8 O ]
& D; P8 m" W* g" b3 E //图片透明度每次递增5点 t/ Q& y# H- q2 Q5 s% D; s- \
0 W; S& Z C. y' W0 n: ]' X m_bf.SourceConstantAlpha+=5;4 @- b2 s; h3 k% M
+ B3 r& d" _( K0 w) B2 e% M //当第一幅图片完全可见之后,显示另一张图片
. i. y$ H! y% A. W
! [+ Z" K# ^- U( L8 F/ U7 E! I& t if(m_bf.SourceConstantAlpha>=200)
: ]3 k3 K$ M6 P: I7 o R- s- y3 ~! V- o$ x
{, Z8 N- A* K+ `& j9 p6 K9 I
: d( h3 h0 `/ `6 {0 ^
m_bf.SourceConstantAlpha=10;
1 b4 Z8 Z6 R; o+ O& e$ v' J- t. @% h1 L; H8 K4 z; z3 L2 M
//将bShowLantern做为显示标志,确认应该显示哪一张图片 ( Y! Q4 l- }4 M3 u) T8 Q; Y7 D, N% C6 t
/ }2 K0 F2 C, ^( q( h bShowLantern=!bShowLantern;2 H% {: d, M1 S, n# b
* \$ t' ~( F' H1 i+ n" ~ }
9 H9 \1 t' f T* e
8 I5 o3 W$ | I9 ~ if(bShowLantern)' `$ Y0 ^" z* y* R
8 z+ x! N9 y( L6 m+ n {
6 `5 H7 D8 ]% k2 _8 F" U N( o$ v! V! D0 p; ^
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);9 ^) W d7 ?. D6 M: w3 U- W
8 T# d8 s* [ g9 I
}
: R% H. Y U5 c5 e: ?- A0 `# Y' \* d* ~5 g% @: V' j# H) q( _0 }
else
: N2 ]5 Z: G$ `9 \/ S3 @! ~( X2 o: E0 q' F/ ]/ E6 L7 g) [" e
{) A( q! _9 `/ K5 U r
9 K+ i' J" R: {' U* F* k0 P$ q; k //按透明度递增的方式显示“背景”图片7 ]+ m! O b2 D" d3 K( r
& U- u6 N! t) E
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
4 q1 N4 M+ @( W5 F# @0 ]8 ?% J+ y
" u, W7 s& ^$ W }8 U m" [" ]2 q. H1 O
! ]1 ~3 h6 A/ | W1 H; C3 A
CDialog::OnTimer(nIDEvent);8 J& |8 S0 A" x6 K) Z/ `4 B4 T
% b: b* \# j+ c; P5 L }8 u! w6 [+ z3 K2 W
4 I& [, X+ Q% C7 k, A* t
+ i2 k9 B, L$ O& n3 n0 S 编译说明2 \6 d( |# p9 A; A/ H. D* _! _ {
: N0 P. B8 ^/ m+ D0 N% y
* W+ G; c0 ~* J; D 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|