|
|
周鸣扬 $ B3 H& f9 G+ U+ F3 q1 w
6 x5 L$ h6 B1 v7 j# n% G
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?2 P9 ~% H0 j. t7 C+ t
* g9 J, p0 g V
9 A3 C9 ?7 j# Z 解决方案& s2 F6 t, q) {9 d- c4 c' Y+ m: j5 \
3 P' B9 e: G# ^) s: |" z/ T& E
x% m+ B: G& r8 R4 ~- H+ P
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
* m" V2 z; G5 r2 H8 [: N6 c0 A# Q$ R# M
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
& I! g/ H- r8 H( t/ ^) s
* y1 [6 E2 J# p w Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 1 T- I. r4 w) `' Z' Z# S. h
/ c: g6 C5 z/ H* x& I& J: ` Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
! a1 v2 d& v& ^ }% K+ S. q- W
' u( T8 j7 U. G" W 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
, |7 K7 @- x- R& T v
. M# _$ d2 D& b+ U( V* H. f( k/ u 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:5 I9 O8 E5 {% [
/ y3 K, p$ {; g9 m% `
BOOL AlphaBlend(
1 H) `9 p( b. W8 J& D# ~9 ]" I }% U% u! e- s6 Z
HDC hdcDest, 5 B) R9 ?% o* E# ?
4 j: P; }3 A N- k // 目标设备环境句柄' V' F( L6 j' W- i' W
/ m# u" T; A1 N* }1 n
int nXOriginDest,
( [' w9 _1 m- K) i' ~: u1 q) N# E
" i% T+ r5 X0 T3 A/ j" V, E // 目标坐标x* Y7 u9 R9 O6 H) u/ q v% T. j$ ~
) q# ?( j9 `5 Y* z* O7 O9 ? int nYOriginDest,
+ _9 l5 D p1 T$ N: a* j
+ T5 h0 y# Q7 S; C // 目标坐标y) X+ `$ z8 n, H3 L7 r5 q. R" k4 d
% o# q! c2 {" q! ^, F0 f- h int nWidthDest,
. d. Q( x1 N" B ~* b7 F
' ]. s" Z. t/ R& ~& K0 o7 K# T" u* K // 目标宽度0 o- M. I7 m( t. f9 ` s; H
6 Q6 r! H; p, I int nHeightDest,
/ j& j9 q4 O9 D* d- r! z7 u/ m0 A4 W) U _ D- S
//目标高度
. ?3 a1 e5 B* \; F: L( j. U& S
! A% Z j$ A3 H+ w0 ^4 c& _, p& T) S/ ? HDC hdcSrc,
+ o0 _# ^9 p) F6 ~' l5 o- r$ P* s5 _6 f' t
//源设备环境句柄
W4 _/ g$ b/ g J3 F# Q* V2 H8 ?0 K& C( o* X% u2 e& q
int nXOriginSrc, # B+ B5 N6 |+ f) ^# b: b: Z6 S& w
3 T* j/ ^+ e1 ^* j: G# {9 H8 k2 \ // 源坐标x3 Y. y2 v. W" [
: m/ E+ c. o+ w* t) H# k/ q% \
int nYOriginSrc,
/ s# f3 w3 f K; n" B
w! N$ U! \# V& h# ` // 源坐标y
1 S, |! X1 z+ f
2 S$ y' J1 e! p0 n: H0 R int nWidthSrc, 6 D, N" I3 q. T5 e
9 g) f% O4 Y# m& H X
//源宽度
4 V. a* {3 m6 g$ h
1 W0 V# G" S) |3 X" ]9 I+ h int nHeightSrc,
3 @% g- ~( H2 }6 I9 e6 F9 b/ K+ D. H* _/ P* \
//源高度
; k( Z( t! o0 i$ W# |6 A3 s$ p+ I# E* C
BLENDFUNCTION blendFunction
0 w$ k3 F( r% s: A: I& f# {( u7 n2 |& }
// 合成方式具体数据结构$ n4 p- e9 u4 K8 ?( f. T! S
- e0 `; W* l5 B O& H8 W' f: s
);
# c/ t) O R/ Z; W. j; r3 k5 w$ X; Z+ d) B# ]: B
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:+ h( y9 `. P) I3 D# t* }
; h: O; r9 O0 Y: t- l$ G
typedef struct _BLENDFUNCTION { {1 B' C$ j2 {0 f
; i4 q, I9 l) ?) Q& v; r L BYTE BlendOp;8 |+ k l# s; _
/ a2 g. S: f* z2 V
BYTE BlendFlags;//必须为零
7 X4 U( {0 D' y4 E4 y7 {5 X& [, |+ F
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
; n! D* K& M/ v; E o
3 J; o5 c% I" j2 b# @* p& H BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA, i2 l% x% T3 {8 g4 @/ ]
% t! |7 g0 S) y* p
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
1 o6 o# a- j* A& k5 o% e' j% w0 e) I, ?1 I, J, G6 u
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
9 O9 a/ R' W8 U; \* m. E5 ]! ^$ O: ?) |$ Y I; j9 G
. m1 p2 f7 ?& ]' x0 A' p 编程实现) m) ]! v# ~; k
# K! V* j. o- j& W, @. {
; F- ~2 L7 ]0 L$ I) ~+ c0 c: G 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。5 _/ w& V# C9 o3 K
/ D9 i1 I `' M" S) P7 G
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明: ?$ o% t& K: D2 K2 Q$ @. ^
6 @9 _! f" T, Y, ]* w5 p
class CWipeImageDlg : public CDialog
m+ }8 x9 y/ o( j9 q: f: ~+ d% p9 V2 R
{+ l' j" V' a3 L& A
5 \4 e8 @9 R( w2 H
// Construction
{% S& l, q" Y8 p0 v" G1 b" n# y' g* Y! m: O
public:1 g) A L1 f+ K& [; E, y5 |% _
+ o; c6 T: f- O6 w( ~4 o9 x4 | BLENDFUNCTION m_bf;/ Q: x! J" d; _! l! V
- R3 l3 Z- `* V/ S& O/ o
CBitmap cross,lantern;6 N( x" G0 T0 q y5 |
4 o" `, Q2 n& o3 E: T
BITMAP bmp;. ^' S6 l: n% v# q% R+ n
) `3 e7 l0 U$ u$ F7 D int bmpWidth,bmpHeight;
' w* m( J y* h8 K; R% M; s* d
CDC dcForCross,dcForLantern;
1 D" T7 B& O- v% B! O c! \; q1 ]) r5 }4 T9 H" p
CDC? dc;
# a- `0 [$ |) S
( O$ ?. I4 ^' Q1 R2 I4 W BOOL bShowLantern;% ], k2 i$ J3 Q$ C3 O
- Y+ j. v: ~- P b, \' ^
………(系统自动生成部分)# `5 [* X0 X0 `8 ^, S
+ Q+ F n% o" |3 O" h: J& `" y1 F
};
% X, a, ]$ z! k8 H) F [) ^) |7 d
/ u. |( d0 ~4 T" x" k& A 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:0 m4 o& l$ S1 R6 w- |/ q3 L. F
1 B6 j+ {1 x T0 `3 q& P BOOL CWipeImageDlg::OnInitDialog()$ D! v `, c& E j
9 r; ~5 H0 l' t" ?* J5 W; W& [
{6 S4 m" m% x: T7 H( c2 Q
& R+ e% x6 c; D1 C. L% ?- i6 @ ………(系统自动生成部分)) t8 ?* g+ H$ B; E2 ^% D8 `. ~
5 s7 _! j; t% a8 l // TODO: Add extra initialization here
0 K0 e5 F3 Q& p* T) Z5 ^! ]" N& l5 b% i7 n
//初始化全局成员变量& ?: R0 e. f4 j# }& L1 K' J
F% ]! i3 _. T- s8 a
this->bShowLantern=TRUE;; u7 U2 p' P9 H& }
7 L3 B5 r1 k c; a1 K5 t m_bf.BlendOp = AC_SRC_OVER;( w, _" r. r+ R& A9 L5 l
C; n9 k; B$ @5 |$ Q, z
m_bf.BlendFlags = 0;
$ n& y3 e7 L- c d3 @
! O" M, d0 S& _, k m_bf.SourceConstantAlpha =10;7 L6 t, A# G0 c( H: U
& I9 ~6 R0 d- p% Z' s7 q
m_bf.AlphaFormat = 0;
9 u: i0 N! H8 L7 y5 f" j) |8 h9 p1 I0 g; {3 Y+ Y8 E
//为节约篇幅,以下代码中略去对操作不成功的处理代码
( _0 _% W. e3 ?+ Q e# ~
9 Z# L3 s; X, e1 Q9 V& b( ]0 K if(!cross.LoadBitmap(IDB_CROSS))
: _- C7 W9 ]$ G' u; _2 w( f
N. f, [! q: O8 ~, n2 G6 Z {
$ @% ]' `9 ]5 O8 J# h- c* s9 h5 p, m$ g% w% r6 L
AfxMessageBox("装载位图出错!");. @. M2 |/ e: G, v8 J ]) i$ G
( _0 k- K% G. ~. S) z/ o
return FALSE;3 Q1 e3 q4 T% m2 ^2 p- U
2 w$ W' n Z% c) g2 t0 J% I5 }1 Y }
$ q, j9 H8 `! c+ B5 W- d; E8 l/ S$ ]5 M* k+ k$ I4 p& F$ ]
cross.GetBitmap(&&bmp);! E, c6 x0 e' X
2 \+ r8 s5 }/ Y; o1 v. U0 i1 e' F lantern.LoadBitmap(IDB_LANTERN);! ` c2 m1 D% L2 S1 d; [ B
" d8 z2 G7 H6 D3 }; |7 b
cross.GetBitmap(&&bmp);0 X* v0 W' ]" c. j1 S* @/ Z* {
) l/ {0 c g/ h: U$ f- X
//获得位图的大小信息
. k: [: K: s! J, G
* J* Z, w$ ]/ F9 v6 K4 C, H0 [+ z bmpWidth=bmp.bmWidth;! u' y% x2 \9 F$ I" Q' x; \
8 ^# I. f$ n9 k' B8 A: b
bmpHeight=bmp.bmHeight;
2 k7 N+ L9 r* o' D; Q5 k# A) @/ ^( Y/ e. m' D) G* G
dc=this->GetDC();- j- _! |- j2 l. O
9 O- U" `+ g6 y. a* R2 A* P
dcForCross.CreateCompatibleDC(dc);0 t3 \ h# a, o* n+ N, Y
G# o8 p; e1 j( ?7 y
dcForLantern.CreateCompatibleDC(dc);
* _ H4 @; W+ M6 A
; a9 C, w. ~3 \ //将位图装入设备环境句柄8 K1 H4 N# A1 T
. j/ a! ^9 ]4 z, z5 Z& O) o dcForCross.SelectObject(&&cross);
! `& \9 q& }; }. ^* s+ y d: @8 B1 j$ B% w/ ~* \/ B/ E
dcForLantern.SelectObject(&&lantern);
5 O5 _% L9 n' S( \. R
: _3 t' m2 B1 B8 A% A1 e1 }3 z: D! A) D //打开计时器$ Z+ G: U- a' g4 }, w
# e' t( i4 J0 x SetTimer(1000,50,NULL);7 r2 z% e' E2 d
9 b) y/ g/ b" e# K* o! l+ z return TRUE; ( H' l& W& w! Y0 z$ S9 F
- \* `0 y4 B8 x) A }! z$ o& J+ p5 k
5 W* V) i7 \2 }% u# N4 v( ~
void CWipeImageDlg::OnTimer(UINT nIDEvent)
% N% T6 m. ~& r* y9 T/ p) }) ^2 E' D
{( N4 ^* W+ o( ?* o0 `% u/ ?
3 r) L# e4 i9 V, M
//图片透明度每次递增5点4 W; e" s/ ~) [
1 C- W" v( w8 y F- H
m_bf.SourceConstantAlpha+=5;
9 B; c/ c* |5 W
- P/ c$ |( W4 [ //当第一幅图片完全可见之后,显示另一张图片
% t$ e# x, S+ Z1 t: C: F- P1 M3 Y ^2 `6 b
if(m_bf.SourceConstantAlpha>=200)
9 G1 K- F6 ^( u$ ^: @5 p" k. M9 w: k% h& Y+ s
{
% l+ o7 X, r6 q! j7 Y8 V/ a2 ^2 c' ?( O* D* B
m_bf.SourceConstantAlpha=10;( n. n6 b2 Q: {0 r) i# p
- C! A$ x1 I# s1 S1 O& M o8 t9 v
//将bShowLantern做为显示标志,确认应该显示哪一张图片 , E1 I9 Z% c* J3 Z+ Y$ G# @5 n
! f2 f5 I! d( w' a# v1 ]8 j; U
bShowLantern=!bShowLantern;% {8 ~2 @9 b9 ~! h! q, f$ g
, I% U% a' v3 B% ]1 v6 F
}( P: R/ b, w1 I4 z6 p# m
8 [" K; A/ k8 {9 g
if(bShowLantern)
# L; B! L5 n+ e( f6 s: S/ z0 ^$ C2 K3 E5 j& ~4 `- B
{
* D& y; Y) C7 p
5 i l0 A! N+ H" ^" ?7 u //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
5 r+ G1 w# z6 b$ u5 Y1 R' Q. x, ]3 \: ]7 j2 \
}
4 B# @" @+ K8 m) Q3 i) e' P( a. u- b" i- z9 D0 R" n
else
! ~1 G& p$ f1 I) s; ~4 s
5 R0 j B: b% A6 K {
) l6 z0 y' `" F" D% k# [- I+ J
) H3 F6 p: Q7 a //按透明度递增的方式显示“背景”图片% a7 z+ R/ @- w5 u6 {
* z5 y$ }; K7 R
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
9 {2 w _1 h! z" z$ p' B
; j! z8 t/ y6 g1 Y2 ^' } }6 Z* Q4 P9 `7 e! X
4 H5 g, M6 J: Y) S) C9 P+ [ CDialog::OnTimer(nIDEvent);4 j, b( }; l" M, i4 A
; L& }/ r2 I; r6 I$ n9 l
}
8 m3 i; [( i% E) j' F& ?
% Q: v) V0 F ?, d! Y% z4 a% h a2 P* x
编译说明+ I% V* x0 [! u% m
* H/ T9 j3 s7 P4 `6 f
0 E/ U+ R R, h; Z$ E% Q- w
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|