|
周鸣扬 . D3 v, H9 i. {2 O
& l1 d$ J, z Q5 l4 x9 o- q* X4 G
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?6 e/ g- U8 y' G/ A
3 N9 |0 z0 o {6 c7 V2 F2 ~
4 _& L, l- q% f7 v( r
解决方案; |/ ~9 V# D. d( T/ @1 [4 \! W) B3 ^
- w% \: c8 F% p( ?
, D# \! p, b& ~# W9 P7 o2 }! d& B7 X 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
& f, H' v2 d |7 B, X, I) c. D3 A
" x: t5 l: {, d. |( p Z: g Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
% g. G, ~: C* l$ B8 X2 s" e
4 _" G* O6 ^6 U$ a# [7 ?: A* ~ Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
7 \' |1 z( G3 A9 u) b1 w
. C3 R6 n% y& U/ o9 m& o! v Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue1 J( A, q- t: K. g6 ^" P# g
5 X) o' W5 |% D( K+ p U 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。8 \" b" [7 D6 C( q( G2 V) ]
: q; p0 |$ a( D5 \* F5 b 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
7 I2 p: F8 L7 O& ?) y( M! r( K
" Y5 Q; |5 Y* ^9 Y! W# b BOOL AlphaBlend(
7 v) g. E2 R- n) ]: |$ I5 d9 Q/ {: D$ f, D3 q; l
HDC hdcDest,
/ H3 `- O# G6 }* b: C% |
3 z" @3 }9 }2 m+ i5 _. n, _, k // 目标设备环境句柄
+ r/ b+ D6 h0 B1 O; f5 L l: n
9 `- v6 q( H# ?1 v int nXOriginDest, 0 P3 h% J% X9 _; m. v8 P4 j! q
4 E; g: M/ U! A) C" Z; ]2 X0 g // 目标坐标x
$ K2 i) y$ _ N) k6 O
( b/ ~5 A7 v9 i% ^/ m3 R int nYOriginDest, o' b7 n) `( R. p& |, O0 A
: w) K$ X' p9 S$ l
// 目标坐标y* L( @, N! B/ Z9 w) }& \
8 A' o$ e8 u5 S. P, e$ ? int nWidthDest, : i& }: ^( O& h4 h/ i
* P' G) @* x! W/ e
// 目标宽度
; K2 m7 |, F3 U, V. n. o0 j- l6 D7 O4 P4 u
int nHeightDest, & n/ n4 [- {9 \) b
( k, F' h3 Q6 e1 X7 |4 n" m6 I
//目标高度0 L/ Z! Y% S9 \' j/ o1 O" Y3 }; c
8 y! q4 c1 H- I5 V
HDC hdcSrc, 8 _( v/ b, v" l e
, D$ \, U+ ?% ?& W
//源设备环境句柄1 C7 i" }: H1 S& K
1 R* e" V7 e( ~" E# m
int nXOriginSrc, ! i. q$ q( E5 f9 ?
* ?+ g5 |9 B% n4 e3 ^ // 源坐标x
2 E, g m/ z _4 ]
5 ?, P4 E m- O& V int nYOriginSrc, 3 E/ V3 Q$ Y2 `
3 \3 p9 ], N3 h+ @. l8 q, U
// 源坐标y
/ ?, A+ A8 `# o' \* {# M& F* N2 ^ P
int nWidthSrc, . B7 O0 X7 M+ L7 b# M9 y! V) }
4 v; V7 D2 ]# L5 T, V; d! N
//源宽度9 D+ H3 a7 ?$ y* f1 R" b) X" ]
$ `. y% R9 V1 d" o9 h6 p int nHeightSrc, 7 k n. k# L" {3 K: L6 x B ]
6 d: J5 Q1 H/ l# h2 Q //源高度9 y3 f1 ]. U0 f: R
& r0 v/ }: z8 ?5 ~( | BLENDFUNCTION blendFunction
0 {2 O u" v, ^3 c/ z7 _# n7 p" U" c1 w
// 合成方式具体数据结构* w: `7 r; @8 r1 e4 K
/ R/ A Q0 @; y% ~* t2 N! R );2 A8 V3 A/ x; G4 T8 R
1 R" Z! {6 `$ T# f" M* q BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
- }5 w, a' Y. X1 m+ l
. ?: G$ r M9 e. v1 S7 e typedef struct _BLENDFUNCTION {& p8 i* m8 q# [( _* F
6 @1 u' w1 p3 @( n" B BYTE BlendOp;
( o3 d/ s! V2 d a2 K" b; o9 G) T$ f# C$ p0 K
BYTE BlendFlags;//必须为零
G( @/ e: R, i: P, T% }: L7 |. e# B: ~6 K
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示$ a! S s y- \7 y
m% I. F% m# a N' P5 |& G j7 l- d
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
( q$ v. a; {1 A5 c. D7 K
( u9 C c5 {6 V3 j# k3 k }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;6 x) Z' L6 H7 \* L
, h9 q+ b: v& A; u# s# v 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
% h3 |" h" Q5 C9 c- u3 W+ l/ Y( l9 ^3 K$ w+ R6 t% w
9 L8 r% k; i7 ~' ^2 w
编程实现
/ z i8 `# C# `4 y) u( Z! J- Q% Q) K" X0 ?$ H8 E, F
) m# M' }, w& N! r- x 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
7 z4 p" ~( z0 z% E( ?6 U
3 [9 s8 ^7 K- h, i9 E" H }; P' w 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
; ]+ k$ e; \$ L. R
; i! {+ X: _2 P$ x+ g6 b8 ` class CWipeImageDlg : public CDialog
- N3 S' K+ t0 U; n% A' Y4 y. ^- C; U( g. j" E; Y
{
( d6 y+ ?0 U% `# l/ ?9 ]) l4 ?& r& a; u, E* ]
// Construction
$ G& c& A6 q' P3 b; v
' Y: q n7 u" [ public:& Z& @) b; `5 d1 X K
3 r( ^4 \6 w' R( V" g& N5 w L6 t) K
BLENDFUNCTION m_bf;8 ^' o- z* j' N/ M/ w
' [8 T( S1 \3 F4 I# g- v1 T CBitmap cross,lantern;
6 P" W3 v0 G, l- _2 j6 y) r7 H; W: m; R# o, A6 ^9 a( h8 ^
BITMAP bmp;9 D% O) o7 U& g0 w& y$ g
/ I% d0 e9 V" H int bmpWidth,bmpHeight;; h( D" _! \5 L) P
& E% h4 h- c1 A/ w, Z/ ]: j, N# B9 I CDC dcForCross,dcForLantern;+ R- ~$ f3 ~6 G6 d, A# D; Z; R
+ m, r$ N( ^& J8 E) r: u
CDC? dc;* i& m G* _1 ]$ W
( m! l- b) U0 H; P* o* ] BOOL bShowLantern;
- r- s3 s" N9 M7 q0 k' d8 i' t( Q+ K; ~4 _4 q3 h1 V
………(系统自动生成部分)
2 M! E& u6 |" i: C
9 M7 g6 s6 n' i8 r, O+ W \ };
7 g- _7 O& r9 V8 [% E1 z
4 `5 P7 h5 W2 R) `( d4 X 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:' c4 u) |* n4 t
$ `* T0 E# M1 y$ I6 |7 S+ |1 S% @! [$ A
BOOL CWipeImageDlg::OnInitDialog()
. _/ b7 B3 g j* N' S. ^$ O% o0 V. r3 W$ D: p! G- h0 G
{2 J/ T5 z4 n/ i, U2 A
; h: V# n! z/ y) g& V ………(系统自动生成部分)* [7 C/ s8 ^* K# U
+ n; R3 A7 J' [3 e5 p/ H1 _. c // TODO: Add extra initialization here. ~/ Z/ \$ t4 m; b
1 p( C' |5 n) P& E5 ?3 _ //初始化全局成员变量
6 y7 c4 E; ~$ O# X& J/ g- b
+ v3 O4 M8 P7 c this->bShowLantern=TRUE;* ^! V; [/ V. Z; P) z8 Q
0 {4 _8 {8 y" P: W) S
m_bf.BlendOp = AC_SRC_OVER;0 |6 d1 f$ `6 M9 g4 C; L% d5 l
& W0 G& R* Q9 @
m_bf.BlendFlags = 0;- q: `& P7 R0 q3 M
4 d# ]( o- v( p5 | Q( C. B# ^ m_bf.SourceConstantAlpha =10;8 T6 I5 n& \7 p3 F/ g4 w; F
% x0 N# {/ o( H# o, i
m_bf.AlphaFormat = 0;
/ I1 ]! _0 m3 L' W1 Z# q; }; C. W1 J% r8 G9 D9 G5 t+ |& G2 J
//为节约篇幅,以下代码中略去对操作不成功的处理代码* Z+ \* p7 k. k/ l' m
% X4 ^! b* r; [; {
if(!cross.LoadBitmap(IDB_CROSS))
$ Q2 E& ^# {& p( }$ q! w. Y
. ]" A% S2 L4 n) d {
* _1 N; p: n, {8 z: i6 u, g; o0 n; ^1 R. p" E5 Q- a
AfxMessageBox("装载位图出错!");- k o7 X9 K* {6 G- |6 a z4 v8 Y
1 n% R9 D! ]! c& C9 B- }9 o return FALSE;7 l6 o& E/ G) v5 a% e' G) `
/ V- N# ^' c- { }
4 \/ v: d6 J- U. C- O6 W$ G- @* X/ o( ~3 S+ [- G- f. S9 B. k4 W
cross.GetBitmap(&&bmp);
- p" ~5 f9 _; [* |5 Z7 F$ B c9 S& b! {- m) Q v: h' z
lantern.LoadBitmap(IDB_LANTERN);
# c* r% y! P" U& `* P7 T
4 z/ g# i. A) I" W: o cross.GetBitmap(&&bmp);
7 L a2 q1 M6 m4 g; u. P) q2 q9 f) y. {+ B
//获得位图的大小信息- o0 h; [3 h' C. P& o! W6 b3 ]
+ L. K, T7 W* I( o/ V6 r. N7 _, G% l4 U bmpWidth=bmp.bmWidth;
' g! A6 B0 T5 A) c6 B' p0 H, E7 P# E& D; h9 Z/ K
bmpHeight=bmp.bmHeight;8 I( C6 {" v6 A# b0 g0 c
( x) G0 {+ C/ C+ q
dc=this->GetDC();/ ?+ F% G2 b, b0 s! b% v
. Y6 C# c+ Z& i dcForCross.CreateCompatibleDC(dc);* ^# P4 w# a s: i& W" q
; m: n0 i5 Y) @9 c
dcForLantern.CreateCompatibleDC(dc);5 u2 A* |) j' z8 p, ^
2 j4 ^! L! c7 q6 N //将位图装入设备环境句柄
; A- d* O8 V' l4 [9 Q
' c8 P/ a% z- @; ^. B; [2 _: a# T dcForCross.SelectObject(&&cross);
* ?; v8 p* {- I: W, F* o8 J/ ~& x* O S
dcForLantern.SelectObject(&&lantern);, G( m6 m0 y9 A7 V. q
4 [6 s+ _# [8 h" L# q3 p% J3 ^2 z! n$ B
//打开计时器6 C7 T. U" D% S A& l' O
% p) q2 |( y3 n8 @! n SetTimer(1000,50,NULL);
; g2 Y9 @7 T0 T( Y: s& }/ d* A# h% l' E1 e9 P. D- G
return TRUE; ( p9 w0 f0 u' Q) B' u- o
+ G6 y$ `4 G6 a3 r; J8 j }8 }) P0 x. ]8 u* A& v+ {" u
/ F/ G4 i3 T/ [5 ^' D void CWipeImageDlg::OnTimer(UINT nIDEvent)5 k; N1 ~5 o" G0 k
) _; f9 X" i* Q. v; k
{8 C$ t3 |; V5 Z% h& [- h* \
# R R! ^1 z, S7 c* U4 O
//图片透明度每次递增5点
1 R$ [: L$ T' ~ ^/ o& y a+ f6 G* A0 M. [
m_bf.SourceConstantAlpha+=5;$ Y0 F- _ F7 C$ A# N0 ~
/ A D: U8 I: ^4 X# A; I //当第一幅图片完全可见之后,显示另一张图片
6 L9 {' j1 d; X" Q5 l+ R4 g! K y/ [% z5 {# C' v
if(m_bf.SourceConstantAlpha>=200)
9 |( |- t3 u6 Q1 C7 Z: M
0 r1 M6 S5 i1 l- t1 a {
* y2 g1 l ^1 X* n- O% v+ Q7 u, j3 ~0 d5 h6 C7 Y( E% X
m_bf.SourceConstantAlpha=10;
& m; Y u O7 s8 E; V4 p4 L/ c# J3 |, I) \- \
//将bShowLantern做为显示标志,确认应该显示哪一张图片 # v/ ^% {4 ~( i* Q' \2 @
! e5 P7 f7 y/ R0 J6 D' c! I% i: P1 W
bShowLantern=!bShowLantern;7 @% ^. u+ _5 A l( F' c5 ~: @
$ K- B* H: P0 { }
1 f" Z3 E3 v) `8 a2 @9 N5 c" e1 m) ^3 m7 ^
if(bShowLantern)& h+ J" K6 e( T& U* W
- ^7 e) T( O* Q% h9 U- L% }) H {
; P: j- K+ r9 u! Q4 P. Q% I- j2 d w2 C5 w
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
8 D0 r0 y; F8 D7 L9 a/ D* @3 ^
9 h4 a% v: v" \) g: `( H+ x: ~ }
2 Q O& _' l6 a8 X! K [. B1 f2 s8 D7 j& S# o4 `
else6 L4 X% w: t, [! h- [) E& }
9 w e; P n4 a, d7 c
{
* t! R7 N/ e H# ?4 `6 O A% V6 G* W5 J% X+ V6 k
//按透明度递增的方式显示“背景”图片; L% A4 K/ P- B- \
% Q1 O) `5 }1 {, ^' l
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);; G4 v" z6 p4 Y( j
1 C4 ]: U% ^$ U8 B# ? p' _ }
; n& ~/ P- R8 z5 h# I* l' l* n6 F
7 a$ J) A: S6 Y7 H/ N7 e CDialog::OnTimer(nIDEvent);' H1 b, x( D5 y/ B$ n, R! }
9 l5 P G% L( _& `9 {# | }
1 e9 Z& w& \# h7 v
( x. k) w, S" a f
& B" K9 |+ r% A R* V4 T 编译说明
% U8 r8 N* H/ t& `* c9 S# e6 z5 k1 J! O
( y0 w1 X0 k" @' t3 Y3 E8 V 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|