|
周鸣扬 & S& ^3 u8 w+ f7 j
}7 e. y! B3 u1 W3 ?4 @' F* w
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?8 X" k6 H, X" G4 s
2 V6 v; u: H' ^2 Y% b9 g: H; R$ [$ Q" H4 q- {
解决方案9 U. d5 s" n- ^
3 Z# z4 c7 j O. q
, e: R3 [ d/ K' o! w 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
6 s% E/ g3 U( [# L6 y5 {9 n1 H7 Q i r: S. \
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
5 h2 e7 G6 T" y# I
& W0 D7 ?% {! o Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green * t9 r, `! @% f
4 v# \) Y- b# R9 [% L2 t% s
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue5 ?) l- w0 [6 G5 j
9 ]; f5 F6 \% _! J: t 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。" u8 P. o. Z& h
6 u r: y. n. e5 O: C 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
+ R) ^, j- L, i, } c& V, H
1 N3 W) z t2 f2 u: v BOOL AlphaBlend(+ S0 C4 g* Q6 ^: X+ z9 U
4 J% s. J9 X+ H# F* g1 r! _' }
HDC hdcDest, 9 h/ @8 d, Y+ w0 o8 ?9 L
+ J. X* K" ?4 X* q ^
// 目标设备环境句柄
4 w6 W; ], ~. d+ X. t0 y+ U+ _ z, V8 H; g
, S. V+ N/ I7 [: y" q int nXOriginDest,
' d5 K& k% [9 |- U. [$ d& Q/ J! Y# @, t' K+ L
// 目标坐标x: k9 F. A; ^* ]4 o9 d8 R9 w' r
. r" H. g% s; v! E: j0 f
int nYOriginDest,
" w8 C8 x( D% H, R0 g0 K$ Q
4 X6 v& A- C, u( q* H // 目标坐标y
$ a* m* g3 B. c$ j. r, }/ y* U# V0 V/ z# m
int nWidthDest,
0 V( z: F# z" D* u) n* t0 X$ } s1 |. @- r9 v* m
// 目标宽度* s: i) O+ g- [* ]6 [
3 r3 r+ [- q3 c* f+ j. F# I
int nHeightDest, 9 B, K- M( Y9 W ^9 q3 k% M& w
) ]) \# g7 j& M8 T
//目标高度 c! }9 T6 W2 t5 L2 Q
, U- z0 @# p" K" C" i1 \ HDC hdcSrc, 6 M* [$ C, ?1 \1 C
9 N# Z0 H# E' f1 T, g5 X7 r' x
//源设备环境句柄 v% X* Z& Y5 W" W% T2 M$ B
6 b3 p" h* z9 u
int nXOriginSrc,
2 Y# x$ C7 p+ [- N
1 M( p: t; C$ g( q9 v. T0 J ` // 源坐标x, G- k$ a1 N* N1 Q
6 V* _ O+ H/ j) r/ U7 P' a int nYOriginSrc, 2 n$ N. D \5 L9 V( a. l; Z: O
5 {& }0 |; j( w5 r, s3 X // 源坐标y- y0 I7 n2 c( a; L
: V% U1 h" `. M4 L: T int nWidthSrc,
- m$ u' r; q G& M$ t# n7 l9 p3 C9 X' J4 U
//源宽度
/ M% w1 R+ ~3 Z2 ]0 {' Q2 S- H; }& }0 v- j
int nHeightSrc, 1 i- ]+ s2 X1 S# k; S
/ v7 N/ g* q A! ~( E4 d7 g+ C //源高度
1 q7 P4 C& _8 n0 ~% a2 k; r
5 N V+ _) a) n5 p; c9 ~% U BLENDFUNCTION blendFunction * o. X5 [) c) F& W
6 i2 Y8 o# I$ _6 R( w) y& e' x // 合成方式具体数据结构
" q$ Y u$ x% I, u$ _$ B2 T# O
1 W+ |1 K+ d' `$ `' ^7 v );3 O# W$ K5 s# \! e& X d
% y5 J0 i9 w# X* c5 I; `2 c3 s BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
, G/ ]# f! q5 O5 Z5 {: K* |/ ?2 P( N3 |; t
typedef struct _BLENDFUNCTION {
2 m: Q; ?6 |# `8 J( [" i V1 e! d. J% M# Y
BYTE BlendOp;
) m, |6 ~" C6 |# V7 w, P0 V" h' N" e. Q* ^6 k: Z0 q5 |5 u! Q; D: g
BYTE BlendFlags;//必须为零7 {7 Y% B1 {' t
5 ~) W% S" b% V* e9 `7 R
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
" k/ S6 c/ |: R3 E/ a: u! b1 @( F
* v: o9 E1 u) B6 o BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA) e3 \- \5 v! v2 |! Z5 w
/ B: j/ B4 S2 ]- v' m }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
% L' @7 E$ } J- s p U* |' n% [+ X. R Q1 B
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。% w- m0 ~( V* R( d9 P4 r4 Y2 P; P0 {
7 b$ M$ m/ g% m) d* J8 r
# _# B1 j. }7 ~4 U
编程实现: _$ ?9 l$ G9 q
( R6 ^- t2 J( F9 d. b8 i* c/ ?$ {9 y' O; I* v# K" J) k' B
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
$ o0 O( p3 ^0 ]! W6 O- y0 r/ a
) g& c- P: J2 d; o3 w r 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
, \4 j# k% Y& O) { m% J3 R1 t
- z% E1 z0 @1 l* |3 Z) r class CWipeImageDlg : public CDialog
0 i" c4 n: N8 R0 q) o/ ]/ r, X3 B! f! w) u0 o3 }
{
& x$ F5 q* C5 K0 W! z4 `1 P
M+ W' O1 \9 d c+ | // Construction
) C! c& e A2 d3 n* L- U! J. E4 w( A" f% p% i+ M; q7 ^& I
public:' m% a9 N7 q" ~* {1 }% M+ l* p
* ~5 ` ~1 q, h& |# Z BLENDFUNCTION m_bf;- t- r8 V [' o5 c7 e
- r X3 |1 p) r: I7 A' k% P CBitmap cross,lantern;' | m0 ` C+ t' C8 j: w& ^4 x
/ ?" h, t8 c. K# b$ U
BITMAP bmp;8 h! I# e8 W5 o. ]* w; ` T
: l# W7 j) O! v9 d
int bmpWidth,bmpHeight;4 A) l* d3 y! k1 L
6 |* G9 m& ?5 }0 s6 E# y. `0 S
CDC dcForCross,dcForLantern;
+ p3 p/ g7 J1 G% T7 F! P& \! _2 u! X9 [! u' v' @7 N
CDC? dc;
& d- s5 }3 H5 f6 {& X9 K2 m
7 A2 i5 }3 \. O C- ~ BOOL bShowLantern;
z+ G D" i. Q# L0 e) e
" U$ [7 R; v0 O4 [3 n( w" y, j* e- a/ m ………(系统自动生成部分)
3 D: b- x' D9 X4 z6 v
$ Z* V' F( {/ ?, D# \ }; ) Z. G" k- \" K" a* \
$ I1 P" {1 [: X
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:8 Z/ j l2 z6 a8 E# \% }) r& }
' }4 R: [) z& \' R# t* J/ u# `" k BOOL CWipeImageDlg::OnInitDialog()0 Z; {1 l9 G1 j' H& D+ ]' b2 z
+ G3 \ E: X% I4 I# C: h
{. C6 H$ j$ g8 \
z0 M8 }& W2 q, p0 ~
………(系统自动生成部分)
4 R# l5 R+ ?* F& Q! Q9 ]/ P% a7 C8 i+ v3 {/ `8 r& i
// TODO: Add extra initialization here2 a; j5 [% o \) f
2 f) f# C3 y% p% M: C' A* T$ C //初始化全局成员变量
4 S' [* c! Y! Z1 l. D2 f
" m+ t, b6 r+ n4 I this->bShowLantern=TRUE;
$ _* w5 g- M( g7 H. S1 U
1 b% m2 y8 y6 | m_bf.BlendOp = AC_SRC_OVER;
+ \) ]( G6 v8 z/ t& f+ a, a% }+ m# ]3 g: H: v T
m_bf.BlendFlags = 0;
- v1 A, R& X" I2 }, k Q3 m; n' S6 O% Q# g- ` J4 G% D
m_bf.SourceConstantAlpha =10;' O' A3 _' e$ z
! `, g1 O/ n/ N4 c8 q4 T: p+ i m_bf.AlphaFormat = 0;- j7 H& U5 x/ ]3 Z% R7 k. ?" ?% N
7 c, p/ f* [0 j; R+ V
//为节约篇幅,以下代码中略去对操作不成功的处理代码
; K5 K. A% [5 c [6 s+ m* a+ P/ [% N' l4 J: W
if(!cross.LoadBitmap(IDB_CROSS))
8 l( h6 N& A8 T' v% F4 ? `$ u/ {
% w0 R+ f9 L: Q+ u7 @3 a, @ {
2 o, ~1 `! j0 S2 R! x3 I; q S/ z& i, r6 p: ^/ Y7 a' e! q
AfxMessageBox("装载位图出错!");
1 D3 B; ^6 b2 m6 p# v) O0 ?4 {/ r6 ?) o1 t _( P
return FALSE;
3 h; x) t1 v9 d+ V8 C; h0 p: H5 M* {8 Y
; A3 e5 B/ G | }7 d$ j- s1 N/ \2 ]8 e, g
, S8 H% b0 g; q. F) Q
cross.GetBitmap(&&bmp);$ \( O1 U- M9 |0 I8 s# N
% E& k! ]* ]" V9 _, v F lantern.LoadBitmap(IDB_LANTERN);
, K, K5 g1 _9 b/ m1 U1 m4 J
2 ]8 @8 T* ]' ` cross.GetBitmap(&&bmp);
2 j7 ^* B% _" h3 O/ ?1 A! u# S7 m! `8 p
//获得位图的大小信息
' g9 g- d, j4 ^* a! e' U
0 Y" f9 p* g" K" R( g1 ? bmpWidth=bmp.bmWidth;. \. U% v& F/ `, D, o# K
e1 I c# e- z6 K
bmpHeight=bmp.bmHeight;2 s0 n1 l0 y- U6 q `5 K
1 j; u6 P% k1 v& m dc=this->GetDC();
+ {2 k/ n* u* v$ s8 A# b. @& S. h- g5 o( w0 A
dcForCross.CreateCompatibleDC(dc);+ L* J: }8 g- J
" w4 r2 F" g* P7 E$ v# W dcForLantern.CreateCompatibleDC(dc);% S) `! y+ ~9 d* v9 @+ ~
+ e% a* D& C% u( j w0 x
//将位图装入设备环境句柄 s) f4 Z" R# }# i H
( }% J: m+ Q1 h dcForCross.SelectObject(&&cross);4 r5 Y9 |2 u9 a7 X$ k9 z
/ ?/ M" j6 R# ] P8 i: c' N; r# J dcForLantern.SelectObject(&&lantern);
% ^8 ]; w: y0 n1 K6 w0 [# @9 S7 ?
' y5 ^6 s* y6 X //打开计时器) K5 t) ^9 e, F# [# @
/ L- l/ b) E5 H8 B9 I" [& d n SetTimer(1000,50,NULL);
: ~7 p6 R/ c. o5 f6 J7 z# }7 o0 {, ?
return TRUE;
( h4 f) o7 N8 O5 I5 X. B
8 x( }0 O# q6 i. T5 O- l }) w# f# {8 f. e
, M7 A3 u* j! K' B& [" t+ T void CWipeImageDlg::OnTimer(UINT nIDEvent)$ \4 V% @9 R f: ~
0 {5 m9 o( f+ ?( W& `' U" V { M; H% x8 n9 P! `0 S$ G% ^+ M
2 p% r0 G ^) y8 a5 V1 ^# X$ M) V$ _ //图片透明度每次递增5点. l# [* n: i* q3 {9 \+ G* ^
' J. b9 f) Z7 P m_bf.SourceConstantAlpha+=5;
: E3 s% F/ _/ C. j/ T' r
3 x9 M6 Q& e8 ~, a( k //当第一幅图片完全可见之后,显示另一张图片
7 e: Y" M6 v/ D& X; \0 G7 R3 l" e+ B& Y6 e! L, f. L
if(m_bf.SourceConstantAlpha>=200)
* G3 m; J; [4 _' P# N/ F& h: G9 C; \. T5 P' ^
{
0 q: K8 Z! m/ f: b7 n0 C
) F7 W2 ~0 C9 j$ S5 }" m m_bf.SourceConstantAlpha=10;
3 R2 U2 U* m$ V$ c
3 ?" T. `. {$ b% @+ R3 N //将bShowLantern做为显示标志,确认应该显示哪一张图片 6 I4 Y/ K) l, Z& k- K
# R* |/ C8 {# k1 H1 E: W3 @- } N V
bShowLantern=!bShowLantern; ]! c2 M4 t& y/ j' O' Q' N; O
7 Q% ]3 p( O* |2 k+ |6 R) Y
}( W M/ I @( ~$ o
* F, Y$ i8 A: Z5 p$ v
if(bShowLantern)
4 ^ _7 ~! q) `$ H
1 P, R+ [; w2 \0 d- j) u1 K {
3 G" @! J( K- O4 _/ Y4 i
' f) v. f% O7 {) M6 m% ~( [ //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);# R5 d' M# H4 j/ s. q
1 h# h* G- T ^& e) P2 a }
- r& A# k! X0 B4 Q
% `" H3 Y5 l- i9 e! r0 c( [ else
4 f2 ~9 Q* {1 A. R& o+ b
" k$ G8 ?5 D! v8 D. V {( n) m) K b' v. F* ~+ H" @
4 J; H" s( ]( }! Q6 k f- P
//按透明度递增的方式显示“背景”图片% @/ a, k, f( O' _- Y k
+ e, `2 @$ E2 g: a1 m& I4 R
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
P- l2 B6 D5 ^) B! t/ s$ R. H3 [ O3 K+ ]2 ^7 z5 B
}, m" w; n; M5 v( t
0 I9 o% d! X" v5 a) m CDialog::OnTimer(nIDEvent);. ^4 T6 j1 ?- G, b/ l
; G) w1 }/ _/ h/ R0 h5 s: T. O }
# P3 r; {# D7 f/ p# D4 ^, c# [
" B ^3 D& t$ N: C8 g
# P4 ?2 f) u) L; q# T 编译说明: j9 n- v2 l5 y7 i
" L: C- y* x3 p% t- {' H$ }0 H' Z5 k6 M; \6 i( ]/ P! U
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|