|
周鸣扬
/ ~7 i6 T" V! {" ~- a/ E" I2 Y1 L$ y( ]
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢? u0 H0 f! y5 m5 I* A5 K
) c( G1 ^0 ]; G/ _0 p( ?
" T2 p9 N% j' [( C n8 p 解决方案
* P. v3 _8 ^+ ]* [1 W& E% j
, k- i7 j7 o w, {6 I, b( P! H r0 |+ z$ J
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:1 h/ r% _% n1 h/ d
6 c+ P, T L% i0 m8 [2 n5 \- O$ c
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red 3 s, ]1 ]9 ^" r% D' E" `
& k# k; s. v" _7 N6 |7 G
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
! ]1 Z* F5 c: ^2 R1 S R; [6 S! @* M9 |6 q6 V, C4 W
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue+ O; H/ ?5 v7 z4 i: }9 L3 g
: _ u3 ^0 F0 D' @ 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' t. L- n! `# R
# u7 H; ~) L- a$ l 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
* e0 c" W- ^& E8 p& I ?* G! Y
- g! [( H/ H! k. W$ t0 v+ I- l# G4 x+ ? BOOL AlphaBlend(: N. s( z9 g8 c' n. V
: u8 T8 z: W8 J" f6 C HDC hdcDest, ; Q/ b/ E( o( H# l9 H, `
! N" `/ r' h& \1 U% ~ // 目标设备环境句柄
. y4 `7 W! Z. z) V1 x
" u1 }9 ?* V; K# A9 v int nXOriginDest,
! A9 |9 J. x2 x* I, G5 X
0 v _& F& P) T8 m6 P8 M7 @" Q9 x // 目标坐标x
* H: d+ s( l1 W6 Y: o: k+ ]! D% {
int nYOriginDest,
/ c! u! a7 F% `( \8 k4 s/ B6 C* A( b
// 目标坐标y$ J# I+ m% }4 w3 P4 y" X7 G
: Y) { Q* r# f8 j6 C) g! r7 L
int nWidthDest,
# x/ ?3 l) {: x. G% g+ k, D7 @9 n/ b8 V& x
// 目标宽度0 J# y9 j- t. ~* `# |# C' d0 X
. L1 `4 B& L: o
int nHeightDest, 0 F7 V) E) I$ H; q. g
! m6 [5 \1 |) f
//目标高度6 r. @1 R1 ]- m; t( O$ I
; X- h6 H' M+ G: v ?* h, L# ?
HDC hdcSrc,
& G' I8 d1 y* L( O
( X8 i$ D! D l; s //源设备环境句柄
" A3 U0 v& J0 n, `7 k4 S) A0 V: r( {7 J( y
int nXOriginSrc,
, u+ \& S& h- V, }+ y3 n1 y
3 Z+ n( j% h( L // 源坐标x" l9 m0 ? G- _" b1 j' n! t# v
1 E+ U) y+ i8 B6 G7 O5 j6 h int nYOriginSrc,
$ W' Z& |3 C2 z; {* a# U. n1 C' ?7 i, v+ g8 ?- s
// 源坐标y
0 G2 L e& B4 B6 M% a1 Q% K$ i g4 s$ e2 f' F, e3 T
int nWidthSrc,
% Q4 z1 ]' @: b2 R6 o6 ]6 J8 i! g" r& E$ l& t$ V
//源宽度; ^2 t: J0 X6 g, z/ G6 w
1 a1 A2 e: G! B- b2 j int nHeightSrc,
+ [: s( U/ j; O2 `. B+ c* j* Z; N5 o% L! c | }' L
//源高度+ o5 Z) c x% I) s4 b j& I
4 J- M6 j& B* j. H5 d! s
BLENDFUNCTION blendFunction
- w8 _# {4 B9 \& R* J6 v1 |
% \' l2 V6 F5 _8 [ // 合成方式具体数据结构
4 G6 ~, G9 u: @- O8 H: D$ A( a
9 q/ J. h K4 ?; K& D );
# H, W, H# |. P# W# L' t/ m
5 I4 v' j9 ^+ G8 O* M6 r& B0 h* l BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
9 n# F$ T- D* S; q( \3 H% w0 g" @6 I7 o: L
typedef struct _BLENDFUNCTION {' J% w$ D0 N: s! |9 K9 f( [: O
! f. @* v4 z7 h" z
BYTE BlendOp;
5 v3 G8 n: H' }) o: Z8 I4 N2 ?+ A9 P0 F1 ?" K
BYTE BlendFlags;//必须为零6 G$ ^, |4 R* G4 E& R/ ?# U
4 J) v9 t, r. E9 T5 L3 o( U
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示" D8 j- b# {5 \* m
& ]" f4 W/ L/ u5 h5 Y
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
# J+ Z1 G+ ]2 V# Y3 n0 `/ Q' O, v2 ?/ [, e$ N4 ]( Y* u6 y2 Y* i
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;4 [3 a4 E U6 y
3 B# F4 ~) {! T3 t7 V2 M# Z 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
. |5 f& j8 B/ F5 J1 W% l6 v1 H1 l, }, P2 N* R6 y
% r0 U- T! Q4 N( {" r 编程实现) B6 y" K' I4 m1 `2 N4 L
" H7 X5 O# k" A* r* _& z
1 R) l. ]# ?) k2 q 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
( [2 ?) d( X! R! ?: `6 }7 i0 `. |# u7 e* i! s+ O6 u7 v' q- y5 y
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
8 T5 P: f4 k$ e5 ]. c, [2 w7 x+ h7 C
class CWipeImageDlg : public CDialog
: _# z- I- K- r7 O& Q7 g. y( y' E+ k% K; q' C+ \8 u* S) }
{
( G1 @/ A" W2 W4 v; E: d( L7 h" U* \. i& L
// Construction
9 V0 {7 }; _2 t! f, S7 J$ B. {4 P" d/ G
public:: O' [& Q2 D) Z+ Z \$ a
# b9 u6 \' Y! ]1 n9 Z
BLENDFUNCTION m_bf;4 q2 V/ t$ n0 s( [; G; L$ @
9 q, _. Y" i* h CBitmap cross,lantern;, m2 i, F, t/ e* y7 a7 [
, @1 j, O7 s1 x" J3 e
BITMAP bmp;) ~" ~. n, `1 {! R* m2 w4 V
: k z7 C/ x0 M/ { int bmpWidth,bmpHeight;. w' E8 C0 }2 c S# |
6 w" @, v! Q% a5 G
CDC dcForCross,dcForLantern;- {# V2 P* j- w
$ u* V! n/ J" H" [
CDC? dc;$ J* R& ^' U3 t) k
9 `8 f; g' n# p
BOOL bShowLantern;
, h: m5 G# X7 b6 U2 c% r- k
% B0 J/ Q6 T2 a2 ~+ [ ~9 R ………(系统自动生成部分): ~: p3 B3 U" e% N
) K& u' d7 E" Q# M ~" o, L
}; p& z. L; ` ?
# G4 |5 v7 a. q3 a 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:, f5 v$ i7 G" p4 i6 G& |7 m
6 y1 v$ ~: A" F* a1 R- z0 H0 c% F
BOOL CWipeImageDlg::OnInitDialog()+ M p: f& q+ D: y S
% U8 e5 r g* E( I+ A7 {& i; U
{
$ a o3 h' I/ q5 q; n' H& s
# T v: _7 u$ d1 i# {; r ………(系统自动生成部分)
/ E' N6 J6 ]6 Z% n/ W, m- W9 }# f6 U- k+ n2 {: x
// TODO: Add extra initialization here, D1 I) S( k4 t/ R' F0 O8 D
& n& k" Y7 M1 m l- r- R //初始化全局成员变量, i8 Y# M, }# U! z
- Y. D. O! z( W* k4 u& _
this->bShowLantern=TRUE;8 ^" D T4 k9 {* r$ A( ]
: `( W# o1 @2 ~) S3 Q) f m_bf.BlendOp = AC_SRC_OVER;
. C; X# e$ u% M+ q% h/ @8 V7 }
: g+ i3 x3 y) @4 g4 i m_bf.BlendFlags = 0;$ c, H5 ]1 T+ `$ X" [
" a- e6 U7 v8 q2 g4 [" k# T8 P4 U
m_bf.SourceConstantAlpha =10;9 Q# s$ E3 F( @* [ p! W1 W3 ~! d0 D/ V
& z/ Q+ V7 n/ V9 I* c
m_bf.AlphaFormat = 0;
7 Y0 p$ `0 Q' [& ~. z& u
7 f- o# Y* o( v6 W$ W7 t+ l //为节约篇幅,以下代码中略去对操作不成功的处理代码
! a& ^- r8 M5 J( c* R! y) n: b1 [4 B; K' Z2 Z t2 h$ {
if(!cross.LoadBitmap(IDB_CROSS))
" P* I. O5 y- K, _! N6 |2 H# g5 g3 j$ Z' |: i, X
{# G* H( ?* I; k2 m5 `' ?$ r
, Y x4 o4 x f0 Y! e1 \! ]1 l$ |
AfxMessageBox("装载位图出错!");
, F5 o9 c) e% s8 K, L& c
: g- t: H9 S; o return FALSE;: `1 f$ M3 ~+ ]: Q* o. F9 m: o% y
" }( C- u$ U" z" @0 Y, x5 k8 N& |
}, [4 N& k, w: c, A( ]3 n
1 S- r! ]4 B: g( W: A7 l cross.GetBitmap(&&bmp);
/ V# l; i" T; n7 O/ t& ]7 y' x- _
, o7 z8 ~1 P7 H2 {) n lantern.LoadBitmap(IDB_LANTERN);
4 [/ W7 T% ^* Z# f" C! g4 a H% u7 G) h1 ]
cross.GetBitmap(&&bmp);# }/ u h. d, O8 l) A, O2 N _( [3 p
% o2 D1 W8 g7 {* A1 m7 _2 i$ v
//获得位图的大小信息
6 f( y/ N3 Y4 A6 j$ r/ _) T& G$ ?) h( i' `
bmpWidth=bmp.bmWidth;) ~( c5 E: f- z8 `7 O0 ]8 _2 p- d
7 m: E2 P' g$ F( ]$ p5 s3 k bmpHeight=bmp.bmHeight;
% }5 i3 V5 n9 t4 y
% k+ Y4 J9 u/ k# y3 L' f dc=this->GetDC();- O! P5 L8 j5 i( u$ o
. s3 m A( T2 I- f% X" t4 g2 W9 X
dcForCross.CreateCompatibleDC(dc);
; `' L9 H/ _' A# J9 O7 z
4 X! `* G* Y! }: J1 y) X2 J dcForLantern.CreateCompatibleDC(dc);
) _# K0 i+ K' z2 I$ [# r/ J$ Q$ d- d/ W; ~3 i: Q
//将位图装入设备环境句柄. t( T. p+ s/ G7 ?. G( @+ j9 Z
4 K2 n# A7 m9 i+ |, K. [ dcForCross.SelectObject(&&cross); R' h9 m( e4 `
) a5 ?6 X0 N c+ g' b" m
dcForLantern.SelectObject(&&lantern);
( Y, z- f+ j8 E* x- k) l" w( Y( y: }* R
//打开计时器+ u% y. a+ w1 F# P4 K) y
: y4 s9 t' J& Y$ ^' j1 | SetTimer(1000,50,NULL);! q/ I, U9 Q6 }( {5 V
! |. R* f0 c3 s* G: a5 N+ n return TRUE; ' X I! F1 L% V, T$ i* M s
! q6 e( [3 E' I9 O% e
}
) z, F/ o: Y$ c2 @* C$ R
* V/ j4 u7 x0 O( x void CWipeImageDlg::OnTimer(UINT nIDEvent)( K3 o/ x5 \' M% `) p
: o9 m+ L! n' f( ~8 J
{8 u: }# G" _1 a# K: e5 V5 U9 {
( ~! Z& t6 r, G# X
//图片透明度每次递增5点
0 j2 E5 o9 y" Y6 }' d1 u1 B1 y. J$ a, c
m_bf.SourceConstantAlpha+=5;; X5 a L' f. s! o3 k6 `8 v
7 K5 b+ Q8 g/ x/ }; ^. J9 A9 } //当第一幅图片完全可见之后,显示另一张图片
# A! {/ k0 g+ D+ Y6 i g! F; X, B: n. O: g
if(m_bf.SourceConstantAlpha>=200) N: f' b: f" S8 x' H& a, h3 o
! j! H& {( G- j. P+ J6 T0 C {3 Q3 f2 R6 \1 V$ @4 V
" x' ?- S& F( o5 f. }* l* ]6 l; a; \
m_bf.SourceConstantAlpha=10;- v8 l5 w( n) N1 y, i' I
/ T$ b1 `7 `/ Y& S( ~( c
//将bShowLantern做为显示标志,确认应该显示哪一张图片
! M3 f5 {7 j/ D4 u& X- b8 n q9 r# }/ q3 W$ f; X
bShowLantern=!bShowLantern;
, }( m3 P- \3 @# d' l; U0 t/ m& o( M) F! @$ j
}; b2 b- g" I' g+ c1 g) D
4 B' k8 ]4 G! J4 o& W( k
if(bShowLantern)
3 j9 C0 P& q: x2 v* y1 q
4 u7 a! J! D$ J7 H w {9 t, O! q1 ^0 f9 q2 R
1 O% n7 ], V2 ^% d% a- T! O5 |5 I6 X //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);0 o; i9 K d6 }+ n
5 d- j# N" y1 _9 ` }
2 R8 M" Z9 p( C* n/ m% z
; L+ C5 s" y& [0 Q8 R else1 i$ ]4 M: {. F. t' [9 m
1 s+ ?+ x6 I2 h2 [% W
{, a# j; s2 C5 Z v
! @- \' H; S* p; J //按透明度递增的方式显示“背景”图片4 A) X) i/ z: T, s
q; e5 a4 L& U5 G" C AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
5 ~" M( i% K1 c* N- Q8 O& Y3 L3 N% w! n Y8 C! y" c
}- j" m: t' X+ l- h& Y) Z5 t- j
2 X9 U- e+ E5 [1 z
CDialog::OnTimer(nIDEvent);0 t0 Q' h1 o" u2 E
5 c7 k7 ~6 X3 E$ J: k7 t( }; I }5 R6 C0 U4 j h0 e# }) y
( C, N3 t' g, \, S' L
1 u2 [8 q2 P6 a+ Q4 c/ p- ~ {3 i
编译说明
3 _. D/ M1 M+ } ?1 v Y' {
2 M7 \" Y' A" w8 Y
, }; ^- W1 W0 v" z0 |3 [ 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|