|
|
周鸣扬 9 {) {, ]% A; f; {' ]
: Z( U6 Z! Y7 F: ]' X 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
6 d1 ^; n, P, Z0 |; Y" o
8 D5 X6 B8 [6 ]
+ x0 o% x- X K9 r% u/ }$ R 解决方案
) E% V$ u, D" L) A/ J
) j( M6 K6 `2 @: L+ d+ r7 A
. W1 ^! \/ ^% X0 Q 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:- n5 P9 J5 _- u. `& ?* f+ n, W4 i
" T( ^; |( P- A1 [9 I3 g) ^
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red & J. _5 V: K+ O, G e
; U( g, E% f% f& |* K+ {! K/ s/ C Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 0 \0 L( m1 q, e9 v1 p- z/ C- ~
- h- T3 O: T/ ?1 @9 @7 n J
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
" z) E5 h Y) S, ]1 Y& d- G
- Q. z" w7 e z. G8 f: ` 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。; t L6 ?7 t: e8 C- U0 B
7 i# |2 A8 c# _, d( p$ B5 g R
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:+ I7 Y4 s* b' S+ B/ y9 D" ?: i5 v
% R7 |+ @$ |+ }9 Q BOOL AlphaBlend(. C6 V8 t( J* u/ Y! F
$ P- y2 ^% `( I; Y3 q HDC hdcDest, 4 w! |% Z& x; F$ P( {2 q& W
" h) m6 k% u1 Y" ~( e
// 目标设备环境句柄
# P$ l0 T# M' s. C" M# o5 Y4 o/ W* }: H" L
int nXOriginDest,
) q/ E6 S' l1 ?7 G4 u
: T: S W/ [3 {5 G // 目标坐标x. @ q5 f% o) g+ A/ _
( d) \8 A) c) P' \ int nYOriginDest,
" x7 J' \0 p' s3 |( O" H3 Z
! Q3 P" l! G6 n; v, y& f' G9 w" ]3 Y // 目标坐标y3 Y) l. L* R( d0 E
- U5 Z5 m2 d5 B' X2 X H1 m
int nWidthDest,
/ [& ]' W; `% s' l; `9 g. H9 q$ F. O y! _2 T
// 目标宽度1 t' b* J: H9 O
# p: B4 }. U% K. F: x3 \ int nHeightDest,
& y1 {# R0 C5 d w" G# o) k' m9 ` i
//目标高度
1 x7 O. @7 [3 z" n# Z+ l& ^% l, }* B B$ C r2 u/ f
HDC hdcSrc,
5 L7 B: ]5 n# z& L
# m3 N7 v' y- B$ y. j G //源设备环境句柄& c- V$ V8 e. M( Z' }1 L
2 f( n+ @8 E4 o& ]" G* Z$ x
int nXOriginSrc,
3 ]* c8 X! ^5 N5 w" I/ B5 s3 g4 K2 ?# s ]2 W5 ]" a
// 源坐标x
! F4 a a& {! Z% ~
- u$ ^" W: M' h$ q5 u" y int nYOriginSrc, 6 S- P- o9 `9 D* N# C
9 T3 R- D9 Q s: z+ L // 源坐标y G; B) Q ]4 D7 l; O) v% H
" R& C( ~ ~/ F2 U& ^# i int nWidthSrc, & ]% t2 t4 R8 C5 g" u- P) g
2 M6 w5 G$ ^8 @, |6 P# @# \
//源宽度
; R% _* C8 S6 [# t0 o
7 n& D! g5 N# Y# f) E0 P int nHeightSrc,
* u1 @' r. r d: s, S9 a; i) J
& C! R* `2 e6 A( N* _; N R+ l' m //源高度
% P( k) S2 N- ?8 w* G& V
- D5 P2 q# L2 a9 E& J' A BLENDFUNCTION blendFunction
% m, R! z) p/ D4 N. P# J; N" Y) y4 m0 K/ Q* T+ R
// 合成方式具体数据结构' `; x, \8 h- T3 H0 R/ C
8 G, w: ~/ N2 [, D7 T- @ );8 {4 x: ^5 |! @' x( ? J$ M. n
% r, m/ k$ |% b3 c Y9 y BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
$ Y) a2 n, {9 M [5 ~: B5 ~5 y2 p
F* {4 k: x: B typedef struct _BLENDFUNCTION {+ j: ]. h# E7 s. P: B
+ F0 H, \1 Y7 U7 F$ t' D; O# U
BYTE BlendOp;. Z/ b7 S* D2 G) z0 L) X
# m$ Z/ n2 a4 @; s8 b( G; ^1 h6 K
BYTE BlendFlags;//必须为零
- \" ~; D! [" |! n7 j# D4 a
8 G2 @! n( o' ?- n# b% a1 B- a BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示# z3 n' ?: s |2 k% n' F
; u7 ?+ u. ~+ c* z0 p' @9 b4 p
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
: w# N; s7 \- }0 z
' L) M1 A2 ^8 P4 a }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;- T, V! n j1 F# `7 b; }
7 N( r4 N2 ?: ?; r1 _
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
% n/ j& W4 X1 V- M( y$ q
) L0 \( R0 x. G1 ~/ C1 C1 E4 L! M1 K5 G7 D; a; g( j' V2 z
编程实现: q! d) b8 `5 T) v" {
4 m, [; J3 z( T+ D) C
0 w4 P; o% \( a! `+ P
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
7 z7 a% g; U5 r1 d& ~8 V0 w) w$ G. i! }3 V0 u; k! ]+ u
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
) a7 h% {" k+ a: t A# b
# j7 `. H5 r$ O6 O- Y2 q0 S class CWipeImageDlg : public CDialog
8 j* M, Q/ w2 B& I$ ?) q6 Q# r4 [# M/ x8 v8 R
{
( R/ @# h: M+ [7 @3 F5 n' O1 k0 I: A7 f
// Construction
9 B: X! G9 v6 x8 I) V/ u) Y/ ~' I6 y" x* S1 V3 X/ t! T: s' T
public:% @6 b4 g/ R: P; }8 O. e2 s2 w
: b2 a8 F% A- K: C: F0 D3 o
BLENDFUNCTION m_bf;2 g( ~# k* n* F* B* ?9 d
1 l6 A0 Q2 q* l, \ CBitmap cross,lantern;
5 V* |. H" b& p8 e% A. V
* U; G" t6 W5 y, J* S0 z" Y BITMAP bmp;
+ |- w, \; L3 B! R3 k y" Z' E
+ F1 N4 o" s! z* d/ q int bmpWidth,bmpHeight;
/ u6 r u/ V, V1 E
* `: d }& ?5 w) b! Q9 G CDC dcForCross,dcForLantern;$ H3 ~6 ^; n% H* y9 \
) m& U0 q8 @+ I# U) q7 c! N CDC? dc;- [' ~. Q2 t$ G) |6 I7 I8 X
+ s& \% x5 T) G; R BOOL bShowLantern;
9 _; O7 @& c" i# `3 p% b; x
1 K' N7 Y8 i; o4 r ………(系统自动生成部分) y. T7 D2 A0 S& J
9 G: o6 d* B: ? S/ ^ };
( X9 V) H1 r) m! [: t/ q$ _" x1 L6 W& R+ O, B1 R, y
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:. |2 f1 U8 g0 A- v
5 v0 T! ]& o4 c4 P
BOOL CWipeImageDlg::OnInitDialog()$ P, \3 o; v$ L( Z0 U7 |0 H
0 N7 Z+ p( q @0 M
{' m/ \5 g3 H9 {% D! ~1 O
6 @$ x: }" A6 B) P
………(系统自动生成部分)1 B. c5 u7 O/ v8 v9 s, Z. ?
* Q! l+ k* }3 M( v
// TODO: Add extra initialization here5 R1 {$ C y' @+ f" B4 W; e7 c+ y
8 V4 E4 w9 l4 F1 w% J; q- V //初始化全局成员变量. a' f; k- f. S) b' E
. I7 L2 K) q9 a& S5 i this->bShowLantern=TRUE;
! }) [5 ?' n8 D. b
, ?, b8 V( U8 b( V/ B; A m_bf.BlendOp = AC_SRC_OVER;' B/ T; K8 l1 n/ A: c; G1 Y
5 y0 Z+ I# \- y! ~- `. c m_bf.BlendFlags = 0;7 K) {7 S) V( j/ J7 ?. S. R9 T: s
3 o4 N6 m/ U, K- Z m_bf.SourceConstantAlpha =10;
Y" ~- p2 z* l; F! `! {8 D/ i( F3 _
m_bf.AlphaFormat = 0;9 r- c1 k; N$ O% z
+ h; ]7 z+ Z$ d/ Y0 J4 i( Q //为节约篇幅,以下代码中略去对操作不成功的处理代码
6 s# C' [' O7 l& N# Z5 l( V: E0 D# x5 X
if(!cross.LoadBitmap(IDB_CROSS))6 K2 W+ ]' O! s% i* e% x: o& \
& J/ T! |1 M( w! ~' o3 S {
4 }" U2 h" g; |; c0 {1 [7 n
- x2 m+ \# }, t# z' E AfxMessageBox("装载位图出错!");* U! b8 D. W. q. r) D* O- G
9 K; E. O0 d0 l6 ~5 c% v% v( z
return FALSE;2 I* A9 i0 h5 U6 t- d
- V# K7 ?% W; q7 L }
& i6 H0 C' d9 }6 n7 t6 T8 T' T) _4 S+ U; ]
cross.GetBitmap(&&bmp);
. B$ z8 d+ q- J4 Y3 ~
1 d, |# K. { l lantern.LoadBitmap(IDB_LANTERN);
( ^4 d* r. M9 I4 T% o
& `- E6 H T5 [+ C1 q) Z cross.GetBitmap(&&bmp);& z4 l( m+ x6 ?3 h
8 x- a# u5 }" E! ~7 P
//获得位图的大小信息
8 \" k5 K* e- g; U2 }
; n. h% V* O( l. n& l }" R# e bmpWidth=bmp.bmWidth;
1 Y/ }4 O' ]7 a, y5 @: d, V4 G4 v# i9 W, F" L, E! Q8 }
bmpHeight=bmp.bmHeight;- f' J6 K% ^, {; C4 i$ X! v- b W
2 Z" }/ g3 t# {6 }+ u dc=this->GetDC();" b6 ]- h, X' x2 H
d6 z- I: r( K" }1 l8 z dcForCross.CreateCompatibleDC(dc);
4 q' H7 u) v8 p" i! ~0 u( H- @5 e# y, R- H$ A0 p, F( |
dcForLantern.CreateCompatibleDC(dc);
. v2 I/ [6 x C% C
9 ~- C9 G* z, ]( Y! M) M2 Y1 a+ w) N: n //将位图装入设备环境句柄
) Y. ~) t, d. \- y
. t2 g) ]) G' A6 J dcForCross.SelectObject(&&cross);% l; w+ X, S: y1 q1 h3 F% ?
8 p" D" c+ T6 A: s9 \* ~
dcForLantern.SelectObject(&&lantern);
7 f: u- i$ a9 c; t+ w
- c M9 G q# k8 p //打开计时器/ j: S0 q7 S. J; U! H) Q
) J, c$ M: W1 B) e7 {6 J$ }
SetTimer(1000,50,NULL);3 _0 u+ C9 S0 d H! M0 T
: F* }8 |0 {9 W \: N
return TRUE; ' t+ w; G; k8 a5 M: G- g* A
/ d4 ^ E" s" ~: ]5 V
}
|1 u' {' c- L9 t! R- D- Y& R4 C
void CWipeImageDlg::OnTimer(UINT nIDEvent)2 o" G* a1 p- s) W8 t3 o& m8 \9 q
2 C5 L# F& Z" d7 n7 m {# g- c- p. ~1 @% p0 ^4 ?9 n
. G# c+ v' G; p+ D5 e //图片透明度每次递增5点5 P, Q* l- t3 p! H' ~
* d. @: H" u% |! D- ^8 O! R. o. l
m_bf.SourceConstantAlpha+=5;* Y# v/ ^( A/ G$ W R5 g% y" t+ F; H
: |7 g4 H6 o2 p% p; Y9 R
//当第一幅图片完全可见之后,显示另一张图片+ f0 s5 O0 r& S( y) A1 i5 L" E! J
' d! W+ N+ {+ C7 d" K
if(m_bf.SourceConstantAlpha>=200)* Y' I4 F6 r3 V- A. D2 u
, p" M. u9 H) j/ ]; w {
7 L4 o# E' i6 I! F5 Y
* K& x: ?2 K; q& K, K7 B m_bf.SourceConstantAlpha=10;
) y8 j% g6 j" H. Z& j
5 ?4 U2 U4 M, k7 b! ^ //将bShowLantern做为显示标志,确认应该显示哪一张图片 c& G# z. \. @% c ?+ P
6 \. Y( c/ U3 x/ \8 A% t, W
bShowLantern=!bShowLantern;% T% g' i+ _' u" d$ F1 I7 j
' p/ O) x# W% j. h$ s9 @9 ]
}
, [/ p2 N; W8 ]2 a+ G5 `2 j1 L& v6 v. h6 p1 q
if(bShowLantern)! g: A: ]' j) G/ W: ]* ^! u
% v0 h% m" {) P& X) v
{
- k9 m, G; d. n" B* A7 C0 y, Y' N+ d! l! v# l, S
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);+ A2 d9 a0 ~) n7 d' m+ t$ G
1 w. J# J# S. a) e: S5 ?# O9 v
}1 P1 N" Z l$ V% M, [
" x8 O) ~" R4 e5 n0 v; b
else% ]+ [2 g$ W- O
: s1 w2 P/ y. D4 L# j' R" {
{
4 `0 J; I+ o1 a0 |, E1 D' M$ F- I# A5 ?6 A0 M7 k; b6 `
//按透明度递增的方式显示“背景”图片
7 I0 m& \, e) K% Q6 W
5 A3 y( p5 s4 E J! B2 L AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);2 D! g( x( F s! T$ y ]
! e8 [' Z+ ^. [+ |9 |
}% g/ t0 F3 [8 D) y0 h5 r1 c
4 J, @; r1 z/ l: \! f CDialog::OnTimer(nIDEvent);
- H2 u( G5 j* ?* c* q3 `2 M7 O
8 I! U8 d" @: G }
/ b( G- M# w+ Y A" q0 O+ f& y$ @0 C/ r5 {
" c; G6 }' K5 f
编译说明% a, [2 n3 M I% ]
# r4 n0 w/ a) Y* a9 g3 E
/ |$ O1 }7 ]) Y/ l. j6 h! {' D" l
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|