|
周鸣扬
% H2 C9 Z) }" \% ~: C9 p) ]1 |$ F U& |/ [; ~. j4 G X; a
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?. z3 D' w+ e! e6 d3 |
9 h) g! c! O( U0 M0 P& ], u0 c `8 n- E; @; z5 i/ R, }! I2 c) E( L: @
解决方案* L3 E3 I' e0 D5 U3 R! ^) d3 S
3 M# ~3 D: O, i! q
$ L$ L1 G6 l, e0 e1 a. m0 `! R 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:4 ~1 _- }' @7 x5 |
4 _7 [9 r0 m( x, N! [
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 0 W, M4 h/ K& Z% J' D9 G5 ^
. y0 b h! y; ` \" j+ K' b Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
3 P4 ^$ n6 h7 }7 C1 ?
# b L0 s- R4 l9 r& w Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
- g R* j) T' J% g- M$ |
/ R$ a) \. r- ~) ^! p 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。1 x& d- \, d- I7 X& ~ M3 \
V; X5 @ J# a N
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:5 {- a1 K6 O. H: D4 z: }
& m2 W7 \( ]; N. N* j& R! v( d BOOL AlphaBlend(
8 K. L2 E- P3 f7 Z7 o5 v ~3 ?( f, ?& i
HDC hdcDest,
/ i: F% m0 c5 e8 w1 _) {
$ D! h j' U8 f# T: o5 Z // 目标设备环境句柄
& w8 I5 _2 n# F/ F; b- p Z4 M) s
, Q+ B! T# F7 |" J( I3 } int nXOriginDest,
\# U7 V2 `$ J1 H2 j8 K
& S0 ^; t, C5 O! G8 T // 目标坐标x
% s5 a: a! N. a
/ b5 L# ^& ?8 H" ~" a. Q int nYOriginDest, 9 g) Q2 d7 D( [" s: ?5 w& S* j
# f8 ^0 c$ `: q' x& Q
// 目标坐标y
$ R$ p7 s: q1 W) ~7 O. c% k: ~
% M1 k. x0 b6 U% u0 l4 L0 N3 W9 j5 N k int nWidthDest,
8 M/ e- i7 c( r! S/ h7 i N. |' m3 r6 j/ H
// 目标宽度) A) t& l/ @6 ?/ ~3 s. d0 C) ^
. z0 E+ W$ M) U
int nHeightDest,
7 k" z0 S/ K5 j8 o$ y5 }
8 M: o2 h5 z4 L+ y4 y; h# j. h //目标高度
{8 \5 {( G" C
9 u# ]5 W- L: E! \3 Y7 f! e; J HDC hdcSrc,
, H5 m! [/ E5 h5 F' P0 @- g. q
+ G4 O0 z) l V# x1 `: V! E' l //源设备环境句柄
0 ~. p: U2 {& G
1 C& H, Z8 `' i, m1 m# e7 G: L int nXOriginSrc, * t3 e- r. u3 l7 j" l) |1 | a; J
, E! r% x L- ?! n4 o @8 d- v
// 源坐标x
) w5 m: Y9 ^9 Y
6 n" v8 N8 p: Y6 i* p* V" }! L" D int nYOriginSrc,
% A1 X/ n0 J3 B' u
$ F0 ^5 }" a- b* x! G // 源坐标y5 V8 k& s, U, }; X
4 e+ n( J% v0 J# ~6 e int nWidthSrc,
: M; ?8 R, t! Q) @: J. I: P! T* s! M2 n
//源宽度
6 [% H" v8 A* p6 h: e" }0 i: o4 f, ~4 I- t# F g
int nHeightSrc,
% w8 N7 n. p* e/ o& k
" P+ M+ `7 Y6 Z* h ^% U: { w7 D( u* j //源高度
1 o6 T0 S( ]$ m. w6 v
3 |# R# _0 G( }2 W! a) _ BLENDFUNCTION blendFunction
% e# ^" K' n3 c" w+ K! T c% R, f0 u) [0 y: l3 ^8 I) d
// 合成方式具体数据结构; Q3 X9 Q U& }0 k0 i/ h
7 ?4 |4 p, V2 A3 k );
2 w6 r1 O5 n6 c# Q8 t9 W( e
5 |9 o# b8 V4 h; d BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:. C6 x: m2 F, q' Q% `* n
! F7 [- k# Z5 E1 D typedef struct _BLENDFUNCTION {
9 y2 r/ Z+ X v0 P- X$ o
6 q& Q7 a$ H; B) P* g BYTE BlendOp;
" u. ?6 L' e3 z
+ H4 @( s1 y6 ]+ w* ~/ |, h BYTE BlendFlags;//必须为零
+ d; H% _8 ?7 N# b4 o3 Y6 w
" X% h" E1 j# F: t) _8 O BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示( b: c2 v. N. ~* {. d- J& z: m% k
! u- v& G; T5 j& u; i7 M+ W
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
- w* B1 a( A- r- G2 b$ j( w
* U. s( H) E. a- h }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
( ? q: Y. e5 i+ n
$ ^7 r: ?$ P+ m# c 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
* Z! `8 F. ^/ s0 x; o7 v* U. f* ?7 ~ Y
* I. P" Y$ ~8 ~9 j6 V4 G
编程实现* V$ E, j+ m) j* S! X
1 H% @1 f. U& U9 O" g0 u/ r [
% g; M( j8 u0 ~" i
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
" c$ E- ?4 |$ Y" g$ j
$ J0 ~' p% L0 A; C+ {$ z! W 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
. R2 O% x) n5 X3 C) A2 l) d& Q$ v
- J" t6 e7 N# Q! N7 u. O0 k class CWipeImageDlg : public CDialog
& L2 Y! b; O( A. w5 Y! ^& [
; M0 n# v7 v- w8 v } {2 @5 A+ H1 l M5 S* y2 H
3 P% U7 d. r8 N7 X0 T
// Construction
# z3 e" K' w2 u1 |0 s( `1 p! W5 y, d+ F
public:
* z: X Z/ ?2 G7 O2 s6 l' \ {2 j2 b
BLENDFUNCTION m_bf;2 {* I4 f' E1 J1 `2 y$ d
% K6 W1 } C8 N R7 X, D1 Y
CBitmap cross,lantern;- {! v" J) h @) ^. a- Z, `+ }. J: G
- O {- a ~, B BITMAP bmp;9 R1 }) z. i" g' Y
5 q; r0 \+ S% i7 ?4 Q/ H6 W
int bmpWidth,bmpHeight;) `) J# k0 {3 u# Q
: _1 d7 R" o. ~" W) d D7 |
CDC dcForCross,dcForLantern;
" |2 y( f0 i" ^! m% H
/ S0 Q {- |4 P9 C7 Q2 W, j CDC? dc;' z) B( Y0 [& m( @: _) P
( m7 Q. @/ n. ^7 O* u E# p; s
BOOL bShowLantern;7 H) {5 I- z* v& j, J
, w1 ]! l3 M8 z: j7 l5 ?- a ………(系统自动生成部分)
$ A% Z3 z2 h: ~/ `! Y
6 z0 Z4 D6 Y; x, T }; + n, r, e1 R6 `* _( U X5 h& d3 J
0 L: @' {6 f3 @ g% F( Q3 o: } 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:" K. A5 n( {$ g5 M
, [3 O8 L5 f" [, Y; x" o BOOL CWipeImageDlg::OnInitDialog()8 M/ `6 [4 Z) J- B% ^9 ]6 g* q1 i$ S
9 N6 n& W' G" i. X$ m
{
$ ?* q; h! E2 M- m$ I+ d1 G c/ D
………(系统自动生成部分)8 h- k8 g3 _5 ]3 d* _5 H6 G( p
1 D6 ~" q6 h0 q3 C. H' t
// TODO: Add extra initialization here/ W' G- ~0 b# R5 {6 l: O
" L0 W$ A1 [& j; r* R0 c/ q
//初始化全局成员变量
" V+ D' ^8 g5 h; V
H3 c" P7 S" _0 E4 R: y- b1 ^ this->bShowLantern=TRUE;% r* X3 |! u: g1 W' W1 N* o
( `, C6 q' E; \
m_bf.BlendOp = AC_SRC_OVER;
; O: m6 k8 t R- A8 k8 a- Q1 p" f' `$ X
m_bf.BlendFlags = 0;- F) t, l# U( H: ~$ R. }" D! d. e
7 H3 m5 f- i+ S0 i
m_bf.SourceConstantAlpha =10;
9 V, q/ D* h7 p* B
c' [8 [1 E3 ^/ z# B6 C m_bf.AlphaFormat = 0;0 n& }0 m3 [" @' N Q
. r& N0 f9 F6 }+ l: a- f/ P
//为节约篇幅,以下代码中略去对操作不成功的处理代码; y: ^( y/ K6 z- p
* o5 J- |) c7 D! N: v0 L, d: z3 U
if(!cross.LoadBitmap(IDB_CROSS))
& b2 k% I$ h' `3 l" O. X6 c6 H" e: f+ R/ v
{
% T% ~ u- w$ x1 {: [% ^" ?. g# ^2 j- p9 l7 j
AfxMessageBox("装载位图出错!");0 c) Q* U5 q6 Q4 Y* ?7 w( x3 d
/ R9 w8 Y( n1 n& B return FALSE;' m/ F: K* P+ q V
4 d- L. d4 ~) u( J
}
& F/ N- ~# v# j2 Z j4 [- D/ Y1 H2 l! q4 l! [
cross.GetBitmap(&&bmp);
: q; A' M$ V' G( O* a! ]4 f$ B# R m: p( @
lantern.LoadBitmap(IDB_LANTERN);0 x7 z+ F% Z0 b
# V% \- I- t u1 r* x3 ^
cross.GetBitmap(&&bmp);& v3 p7 p" _3 ~) o2 e
- c+ \& k4 }" W- G5 L
//获得位图的大小信息
: x v5 X: g$ q) D! ^$ [( f7 l4 a! a' O5 r& D2 X& ?# c# {
bmpWidth=bmp.bmWidth;
i8 G" T- U7 @8 N- ^7 M) h* O$ ~/ q5 x0 K( y$ @6 Q
bmpHeight=bmp.bmHeight;- y( W3 G9 b. |) b
" P) J+ g) S* D4 s3 R
dc=this->GetDC();& F# \' l( r# K" O7 N& R/ M8 i
* B& v. W' L H+ e dcForCross.CreateCompatibleDC(dc);
2 e% A8 r$ q. o) L$ e
. O9 E, ~% j& }; } dcForLantern.CreateCompatibleDC(dc);
1 L/ v( z, [# E0 ]0 @# o6 r
# W. J& S- _. x, ]4 [ //将位图装入设备环境句柄
; z! o) ^# X1 } u/ f+ q2 }$ n w) }3 [% ^- @
dcForCross.SelectObject(&&cross);
+ ]- v8 N* ?- }
/ H S6 S! V( { dcForLantern.SelectObject(&&lantern);" O; W, d5 f. O1 x3 L( h; D
" _/ J M8 E: f W" T0 A$ v //打开计时器7 r7 v+ J5 D+ p! N$ B: G
. k/ y5 T6 y& I. w+ k4 h SetTimer(1000,50,NULL);
/ o' S) H- U( c) M2 U0 x2 t9 I( R. ^9 i, a8 j$ L; h
return TRUE; 6 K% e8 q/ O& V$ U1 L7 o q& m
, e3 K/ t Y5 z0 \7 i3 o
}2 y! v5 T" U) d+ B. e
$ n8 V. d/ o$ s. J8 U2 C9 I$ b6 I, e2 w3 I void CWipeImageDlg::OnTimer(UINT nIDEvent)
. C$ W! G% v3 t/ u% {/ h/ K, j6 Q3 V8 ]0 v+ Q
{) u4 A+ x3 G7 q6 a+ Z9 q0 s% l- I
4 w# @6 D2 x- Y6 E, R //图片透明度每次递增5点
9 d. \. |, `7 u6 p
* X% |( v5 L! V1 { m_bf.SourceConstantAlpha+=5;
; ^+ X: F" W& n8 i$ Z0 G8 X4 L# p8 o8 b) {6 m1 R) e. C+ ^ L, |
//当第一幅图片完全可见之后,显示另一张图片: |$ _0 c6 r+ Z1 P1 C/ G
/ M2 v8 C" u. X2 e# x. X: M) V
if(m_bf.SourceConstantAlpha>=200)
8 S# n2 P! p4 H
1 `# |% O, ~. x( L' i9 X {- V: V9 k. }3 R$ ^5 D0 w3 b
. w( |6 h; Z% ], ?' M. o: D m_bf.SourceConstantAlpha=10;: n+ u: H8 F" g; p1 d# s
3 B2 v3 V1 c3 P, ?) q5 d- h0 b //将bShowLantern做为显示标志,确认应该显示哪一张图片 3 u8 k7 m" E. N9 \
: u" m8 C: r6 M$ H, g! H3 f3 x
bShowLantern=!bShowLantern;
$ G* e6 J" J z' U- K7 x8 Y6 b" p& W1 e) }" U" ?6 U
}
- }+ H4 Y4 j: o i- n) n3 _& m d
: ?$ i( L: i g+ ^+ a4 \3 o+ _ if(bShowLantern)- r$ Y, x' M+ V1 z7 e6 M* r4 e
; G1 ?, |, p1 Y: u4 k2 r
{
7 K# N1 J! I. [( f1 F: W$ b! [: K0 y3 ~* H# i4 g3 W
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);6 o; M* h3 ~* x
. ~, q- G) P) X, x L8 F. { }
* H H- V5 @$ t* e4 R+ y, W7 D7 _( q0 e
else
% M( S# B l$ g/ `' d
1 u9 K! `: V, b* i9 s; `4 F$ K- x {
$ v9 s6 N5 S2 [. n5 }
0 d' @! J6 [+ ]. p# [. d //按透明度递增的方式显示“背景”图片$ m0 J- A8 `+ h( ], z
1 {+ K# R7 \/ F* D5 C, y4 H
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);9 h( g& v1 w' e7 f1 W$ M
- J% n4 V: h* a, ~7 H
}
1 p& s, F U" \: a* ~3 q3 k8 g. l( m0 w
CDialog::OnTimer(nIDEvent);# ]2 t+ P, c+ i* Z3 }0 t# H
! O! Q2 \- Z R5 K; c: b# ?+ q }
: S: M% o8 o. g% I
) X& j2 T8 c1 \/ \, l- G1 `4 V& r4 j6 Y; j# ~
编译说明
; Q# q: x) Y$ L* r5 C
& N5 c" W; c9 A1 `! j/ W7 m
4 s2 V' J' U! D% |5 p7 O+ T 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|