|
|
周鸣扬 / U2 O/ w" ^$ R8 ~1 a8 R9 s
: G. z5 s6 v# D, {3 v; V 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?# W1 A' |) T$ L O/ ?
& C& U1 T$ P0 Y/ y- [
9 Z8 n% `% x& S! M# t. k% y1 @ 解决方案0 k5 k5 B6 [5 K( I
; [- Z0 V- h2 \' I4 ?
1 Q- u& L3 z- t
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
0 B0 ~* h0 }/ i+ I2 P3 E5 A' L; I6 }0 l9 t& M/ j, L+ t
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red $ r; p. K: b4 b) X
8 B* n& k" v7 H8 K1 Y9 U
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
$ l L% S, L4 G
2 `6 z- x0 p" ~' j Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
6 h) d5 L" A5 V9 n
9 S# d j' ]4 {. c0 z n4 B 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
( ?2 V. s( f& @9 C8 R, d2 m) e
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:$ y+ j5 t# i G; ~0 v) a r0 p
% J) S b# b. J4 ]0 Y, ` BOOL AlphaBlend(
w' c: w1 X( {% T
+ ^5 P D: V# d) W3 h3 X HDC hdcDest, % d% R: p3 }/ A2 D
3 x: {' r5 `4 a8 h' ? // 目标设备环境句柄
`5 ?, e+ ~ `/ Y/ a: q0 y' H7 n: i) s- z
int nXOriginDest, , p P3 [ `3 V- d6 X
8 e! y2 Q! g/ w' G
// 目标坐标x
1 m8 @0 ]1 X( B' z8 a+ G2 p
1 Q6 P U; q n# [ int nYOriginDest,
; \+ K" M. Z" u$ j
+ O- p a }5 g& m, {, j // 目标坐标y. e* j1 g1 N x5 }" [ S8 f0 A
F5 R! X; t% y4 B5 J
int nWidthDest, " C6 q$ J: [ z& ~5 V3 Y6 R
+ P0 J: G7 `0 P$ j
// 目标宽度# ]6 b) N# s; w! B
5 g" W3 H; P; B
int nHeightDest, / ^/ Q) e% y; L6 y+ G/ d
: t5 _3 g. I P# a //目标高度+ T3 C) u) r! L, `1 E* Z% I
- i/ s h' _' D3 s" _1 E& q HDC hdcSrc, G2 [# I5 I3 j: V
- K+ R& h2 t6 [* e
//源设备环境句柄* q+ v1 _" g0 d. U& a4 ]
7 T: r5 V# }1 Z" r
int nXOriginSrc,
& B* w7 I5 P! h* d
9 y' p. W2 x' a4 D; U* J // 源坐标x4 b" i$ h/ L/ t! q% d
/ r8 R% R" V6 ~/ n( u
int nYOriginSrc, 8 B0 U5 m/ H: r, C- v' b# K$ M. @
- x4 I( r$ \! W
// 源坐标y2 }9 r: M1 j6 o9 s6 n) O
2 a! A' t0 ]6 q9 x8 i
int nWidthSrc, 4 B5 C# N- k* R4 t+ I6 r! }
% k$ i% u, E/ u8 r- B( Q
//源宽度
; P; o7 H9 Y2 u' i; s1 _3 Y4 Q0 {. c( T U2 D: U8 y0 b' k) I
int nHeightSrc,
2 J6 t' y7 M- d; }2 T6 d, \" U8 w) B4 k( J' {: q( b! m, S
//源高度
; W, ?% Q1 t/ L; j9 L: D
# ]0 _+ ?' F& N) y: }; d& s6 N BLENDFUNCTION blendFunction . I8 v% C2 R G, t% D; d7 f$ z( @
/ C0 |2 q, d) c. O+ F, f
// 合成方式具体数据结构7 o: V& u8 O u
. z* ]! f0 A1 z* @4 z- ]1 h% _: q k
);
/ `! W# p% G. O5 J+ @' c7 ~
& E9 w1 |' Q8 o/ T# u7 t BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下: ~* I) H; h0 v/ J4 r
9 v: y# C& U& ~5 A' L typedef struct _BLENDFUNCTION {
- ~% q0 H) \* p. ^
" F* z0 V8 r3 B0 s BYTE BlendOp;
' c; t7 P/ y6 E- y( i% s
& t' Q2 m+ Q4 a4 }# w BYTE BlendFlags;//必须为零
+ Y3 W9 i3 B+ A7 l( ~7 p- I' w0 y' T! r0 I" e9 o
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
, j1 j0 y/ r) ]' B6 Z$ C( V0 k5 g! A* V4 k# O8 v
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA0 J1 V! ~: @* }1 Z4 h
& F( S: Y. m+ @' A2 l: \4 g6 | }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
6 c6 e$ ]- z3 _, w/ ]
5 r. c2 n/ }7 w% J4 ^- M* _) J 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
, S( B/ \! r$ G
- [/ ?2 x0 d" z
: Q3 U8 X! k+ L- }' I1 F n 编程实现
' l1 ]) |2 \, L. c/ X$ ~ d$ {
+ F8 Y1 }- [" v$ C% p' d6 `8 c+ ]. X5 _6 ]# W8 s! }
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
1 M# ?" P+ I8 x1 T. U6 V& N: i1 P9 ?3 M+ n" M& \" f
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:/ {- E) J5 t7 B; h* m
8 A* z) G+ {2 L7 V5 F( j f9 I class CWipeImageDlg : public CDialog! a" D0 f/ j: X4 H9 s. C7 @0 c/ t
8 q/ z- {. {- v( ~. Y$ u
{
4 o. h: Y5 L- w3 w( H: q8 E0 K; Z2 `( _
// Construction- ]( S* p; z, ^6 ?- R
/ b. r# p1 q% n: M& x
public:$ z3 [& {$ k8 W# L% i3 F* i4 T
' c" m N1 Y1 a2 |# F( U- ~) D- Z9 f
BLENDFUNCTION m_bf;, w" f2 p; G5 _5 q4 h: U# e0 U$ _
! [0 i3 Z, x9 ^1 w4 E/ K2 C
CBitmap cross,lantern;
3 C8 w- d5 S: {, Z' W
]# R" R: S3 \6 I+ W BITMAP bmp;
8 f! @( @. f7 Q' }6 T# U9 `3 z; M8 H: ]$ r, {+ [+ ^, o
int bmpWidth,bmpHeight;
- d) F8 U7 s- t& x7 e, D
) ~" c" o3 c" H6 y5 w( G0 `* K CDC dcForCross,dcForLantern;
% {( ], Z3 c; v* \3 _9 [6 \7 H* O, Y9 E% ~1 ^
CDC? dc;
/ V1 L* d1 l. F% P! x+ z- f) }* p: e/ O% N% D* ?3 T/ i1 M6 h8 K' X
BOOL bShowLantern;
) P9 l6 w/ ~4 n: G9 O
! }5 k' x1 v" _! _1 N& d ………(系统自动生成部分): r5 B! e" ~7 h7 x5 n( m
2 y! T/ V2 K2 V h( _1 V9 W: A; y
};
" {- q" J( [4 f- G, c2 Z: @' V) P7 g1 @0 n+ M) K
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
. b3 a" ~4 ~% l) d( ?
) G" b1 z% x0 V# T' Q BOOL CWipeImageDlg::OnInitDialog()* z' f4 o& U5 ]5 {. q
0 g+ J3 W0 A3 m+ f4 Y9 l' e {9 p! y- k# D1 P( [
3 d2 L3 g3 R0 ~ y ………(系统自动生成部分)
: l3 s+ ?. }; `1 j5 \4 X! Q' P* {8 \$ J* n# `( x1 g
// TODO: Add extra initialization here
2 m! r+ P) i: Z/ e. {
) h5 r6 B1 |, R) u9 ` //初始化全局成员变量
4 C9 `+ i6 L3 R3 |/ R, u& m5 @( ]+ E( F7 y4 ] Z. E5 E# b# N8 x3 h
this->bShowLantern=TRUE;$ T g; }7 w6 q! _- G
. ^/ F; |, o2 G4 k m_bf.BlendOp = AC_SRC_OVER;
% ]! c' o4 a; g# q7 v! S/ L
" j5 T- a: \. c* N' { Z' Q m_bf.BlendFlags = 0; ]1 }2 ~- V* _4 V4 M% x8 ^+ e
8 O) j, ]* V7 J+ ]8 ~( | m_bf.SourceConstantAlpha =10;" i9 e6 U. B5 L+ [
8 r9 ~6 B4 ~" y+ y! _. e& A
m_bf.AlphaFormat = 0;
1 Q9 s1 C* l' i- s& Y& N& g8 L2 T1 t$ k, Y% H8 \7 c- b7 q
//为节约篇幅,以下代码中略去对操作不成功的处理代码. ]' @; Q" T% b
1 j6 h4 r9 j$ @2 ~% K* t! O" L! Y if(!cross.LoadBitmap(IDB_CROSS))4 _0 X9 z; A# S7 m- ] D+ L! z
$ I" R0 i; u# k& S* o- C: R
{ ?$ y2 d3 C; X2 l# P8 U: N4 d
_( W- M3 O9 R& Y# b8 u
AfxMessageBox("装载位图出错!");
) g8 d- h( A v/ r3 X/ H
" [' N9 ^3 S+ ? return FALSE;
0 q1 V B2 I8 y4 I" R0 P' {5 r& e( C: b/ L4 s! d! W5 L
}
& N9 c" B. D# \
$ c; d' ^0 C0 o) o. f7 G/ b cross.GetBitmap(&&bmp);
4 Q1 `' D2 o. N- ~2 {. { P
+ f# K- \9 Y- N lantern.LoadBitmap(IDB_LANTERN);0 I) g2 {( K7 s, ] c, |- \
, J% v' M1 V$ V% U8 a) }* p
cross.GetBitmap(&&bmp);5 q% ~% ^/ \) K0 Z+ u* ~
% U) L g6 f1 u. } //获得位图的大小信息4 z$ {* ]% F( t+ _% o" m; }; u
8 E8 Z4 R, p- Q bmpWidth=bmp.bmWidth;
9 b8 ~) h4 _0 l* U2 u, u1 o: p& z; j: x) ^1 D9 B
bmpHeight=bmp.bmHeight;
8 ?' {3 Z! I" G2 B8 u1 U; |
4 N$ x# s4 e- ~. @: t dc=this->GetDC();
; K4 r& Z$ u) N/ z5 q- i: |9 E+ O$ S! t2 ?& N; L# L5 w
dcForCross.CreateCompatibleDC(dc);
' s0 k$ ~0 |( D* c
1 s; T7 R8 @' l( i9 e$ v/ k dcForLantern.CreateCompatibleDC(dc);
% |6 f% x8 P+ J/ f% O) k$ o$ l; x- ^' j, f
//将位图装入设备环境句柄, J6 z, D# t4 k/ f( `1 z- ^
1 u+ C; j& y6 U dcForCross.SelectObject(&&cross);0 \ U Q$ s. ]7 P" E
; S W' u @* X/ A' e dcForLantern.SelectObject(&&lantern);& C- n: m7 d: A+ F5 `( M7 p# n
% A. P8 P7 P4 @' M' Y( b) a1 z
//打开计时器5 t0 s5 g4 c: j" F0 p, o6 |
+ N$ D/ H. h- D SetTimer(1000,50,NULL);: W$ W- U6 n6 |( }' t3 h9 p. W
, O2 C. q& {4 Y
return TRUE; % G! s: J1 g' R8 y# N3 s
$ d, \ s C: I* f$ U' ^
}
3 G! h- Q0 U6 s) O& L
. y) f: N7 A) O void CWipeImageDlg::OnTimer(UINT nIDEvent)- M6 i) V5 Z) _7 x7 C9 Y/ m! U8 l
2 U/ Q! r9 ` T4 @+ k; [ {
/ k; r4 m0 [6 U4 K" y! y6 z
8 i( |# y# d; P //图片透明度每次递增5点
1 [7 ]2 ]) g! [- u" U( `' F( Z. y
% Q/ [4 \$ W3 ~8 q: M m_bf.SourceConstantAlpha+=5;) Z* @4 }' A1 }2 y
4 R: u* w- ]; x. t8 p2 a, h
//当第一幅图片完全可见之后,显示另一张图片
1 a) U* z4 n- f6 P/ e9 F& v; G% _! I; R2 g2 g: n! G% @6 U1 x
if(m_bf.SourceConstantAlpha>=200)! \* o% z ^6 H) a8 H* @
& R8 _& u3 N5 f+ v0 s {
: F9 K% v, {$ m8 W8 j$ ~4 \
& J3 z( A& _) }/ i" W& r m_bf.SourceConstantAlpha=10;" |" a9 }# p) s
" |7 B2 I! P/ ~* H9 u6 B, G
//将bShowLantern做为显示标志,确认应该显示哪一张图片 ; ?. t+ p' Y5 U" y$ R
, n/ o: E& w$ d, ]+ [ m) Z
bShowLantern=!bShowLantern;8 ?0 _: l3 g; n7 ?
* a7 k3 B5 n' j4 Q; V }2 B0 O! P- u8 x4 b9 P9 `5 d
7 q, c7 C# |4 F
if(bShowLantern)9 Y& J7 q2 o3 A9 h9 L; M
( |! {( `9 u: K1 K# C% U
{6 \1 y$ _: p, y" S
! v# r1 b0 R' R( E1 J
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
! S5 S! ?/ G' C/ A: w/ f m8 k
4 E, Y8 g B9 \2 q# l$ Z }
" m+ c s+ o: W2 ?- {2 e8 }3 F% U9 V0 s
else
/ [ s# v( {, q
: s2 ]8 A4 k7 k9 } {0 G2 C3 R- S2 a! m0 z
; ~. k1 e) h, l
//按透明度递增的方式显示“背景”图片. D# n2 s' o: F; ^* u
" R1 m, u7 t7 P AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
( B6 B$ D# W" v7 p8 O, m) | E9 D$ d" o) F
}7 g/ `4 i5 R6 k2 P
- ~2 w1 I, j6 r7 F1 F* E0 M1 _
CDialog::OnTimer(nIDEvent);
/ L& T6 E5 r. ` q5 A& b. F% d0 h+ S9 d! ]6 [9 D9 x# p
}
% _1 o, R; O. }7 u
* |8 X" m3 Q/ u, f; f" o
) S' L3 Y9 \3 v. p$ [ 编译说明" l1 E) U, W! A+ N4 g
! z/ d) e* K7 v7 |
! V+ P7 G t/ o* Q! E+ n- N 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|