|
|
周鸣扬 7 f) @ }9 ^7 t- @; f |( J5 n
9 J$ g) O! J/ B# s. ~/ W
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
9 k9 Z* K% l- K6 p, g2 W1 A
1 B6 r0 K/ \1 N. q5 U, i- m% ~1 W8 ~- z' j: I! T' ]. d
解决方案3 S4 T+ b+ C, m ]
; W0 O7 N' C4 o7 {2 m6 H
' Z6 ^# O0 r0 d8 M! p
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
% {" `* O ?0 [" i' c- J9 _
* c: b& N V. R Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
5 n2 Y9 p* S8 L: `$ @ l
% r, w2 S' u3 T0 Q' c# F Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green / W$ | ^# h5 S- y2 f, [
5 r- z& |: A7 o( e/ d
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
: z1 o- @# z: j4 |
{% ?; Z7 H2 \& O8 P 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。6 }' r% }2 d7 }
# m2 \; }0 p- v0 g 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
$ L) z- h$ P" J" a) l% _8 s& a( c/ U# f! h4 F% ~' K0 h$ d8 f
BOOL AlphaBlend(+ l5 m4 Q0 P U# s& \, s7 E
( s/ L8 r4 F4 C0 Q1 I$ x% a0 p
HDC hdcDest, + \- V! N: e2 u
7 [" U2 e& B2 k
// 目标设备环境句柄. I7 k3 S# U. [4 T& N* D
+ z# g$ M, w6 S
int nXOriginDest,
6 P& ^8 c# t D) q& P; t, c$ v% U, f: r, G( }
// 目标坐标x
s0 }0 Y% }' D5 `7 t4 Y2 Z7 D9 ?- L
int nYOriginDest,
( q" d; V; t3 \, j4 r! o1 O" U+ I y- i) N j
// 目标坐标y1 C0 f1 k. `; n! s; O0 {! e% v! u% k `
J' R* i- }5 A5 x7 ? int nWidthDest, 7 I- J4 _) ^7 {
: E3 ]5 N; w7 Q7 u
// 目标宽度
9 D7 u; \7 R3 m0 ]; [( N {
% Y- R9 I+ V8 `# k/ T2 m1 ^ int nHeightDest,
' ?5 y# f5 R7 x4 V4 H) o" j# R# y
//目标高度5 I6 F0 A6 a G( W+ t
! x. y+ _" g3 t2 L% ~2 A1 j+ L3 h
HDC hdcSrc, & w: a1 P. ^" @5 r) U+ `
! V4 g5 g$ F$ k' f
//源设备环境句柄, G" Q9 N" [2 H1 ^, `3 f/ O
1 Z7 R9 G& Q9 m' Y int nXOriginSrc,
, Q9 }8 E3 e3 p! Y. ^, q: N1 e# S8 W4 i0 U
// 源坐标x
$ N1 {+ ?9 k4 ?. L5 x. n( L v; t/ X0 B0 `& Z
int nYOriginSrc, 7 m! Z5 D" t- ~- S. o- ^" G
$ z6 a3 H1 n2 v7 q // 源坐标y$ q5 i+ u% y6 I7 n
1 N# O( V+ f; ?+ ^, v int nWidthSrc,
; m) @ E/ e7 V7 [; f0 i
4 z) y( q) k2 c* N6 @7 ]% z //源宽度& f3 Q- a# t6 Y* Z% I7 L
" n2 t7 m6 b8 L2 r" n int nHeightSrc,
4 |2 n6 w. q1 _+ E1 g
5 x2 U( D0 J; c9 ^4 k4 Z# V4 V //源高度
5 c. Q. q& f6 V9 Y Q9 `* @# w& w$ k+ p$ g. D
BLENDFUNCTION blendFunction 3 V) m4 L0 S. I9 k
6 U7 h& n0 H! S: N8 N+ p // 合成方式具体数据结构8 m5 h8 l4 B- V f" N/ w
! Q4 B4 _$ }+ m( B* Y4 Y: p6 Q! r
); [+ O+ \0 v2 P/ W
: a" y7 ]6 E/ V. Z: o
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:+ J5 h4 G( m' O2 _% }
8 B) D6 A& S! N5 M: D typedef struct _BLENDFUNCTION {3 @, z( k6 ]. T1 D
: U% p4 [: @4 {( T2 ]+ y, T
BYTE BlendOp;1 L0 F( G5 Z' b- V
" m. T2 {; D7 a7 B" v BYTE BlendFlags;//必须为零" O3 j- \& R; ~
; v) ]& C% K: `' w: K
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
: j; I" c9 V& ^5 R5 x, U9 E5 e2 k7 G3 V& d9 t; c6 u, s/ |8 c" q! [
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
6 j) U& X( Q& C% S! @" V/ S+ \
: r' } w- }- r F8 L5 \! X( _ }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;/ {2 J+ U7 l( ^
4 x3 T& Q* L6 g6 [5 Y 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
* u* e4 |6 c+ D# |# \" ^' r
; z5 V9 `- e r0 j/ s
( f$ K( `! r; | 编程实现# s9 q7 l& i) G! }, X2 R
4 _( A; m% n9 w$ P, A1 K: m
5 x! q$ S( A" D 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
6 L* r4 l5 R* \0 y0 Y( [0 K6 T, R' a \! v8 P( j1 m7 c
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:' \) x2 e$ }/ U" n+ `* q$ g
; S2 ^. T6 O0 t+ ^! A% H class CWipeImageDlg : public CDialog
/ U7 o z! {. W; O( r$ g U, k9 x7 G. r
{) Z( }& v) T0 X2 B; `. n* C, o" C
9 c& q: C( I! J( v4 w
// Construction
6 r; P% V7 m. |9 w' n3 y5 {; I
2 [( W! U: R2 B' e: B' ]# J public:
' H* }1 U: g4 j% H* r% o6 V& }* j& l1 {: O) O- |7 G) W
BLENDFUNCTION m_bf;( _3 N- J; v S! C$ i! l- `, o4 T
8 v4 R6 }& a# ]- |! [: ^8 c
CBitmap cross,lantern;
; _6 I6 B4 A( n. z% M2 M: z. C
8 g0 w/ d, P& U/ Q+ L! A& i/ p BITMAP bmp;
0 B0 M% X% }6 b
7 A0 N7 ]) r5 P7 f: E int bmpWidth,bmpHeight;
4 i0 r3 K* ?% n" C3 q+ K; _) k3 A y2 a3 x0 Z3 z
CDC dcForCross,dcForLantern;- D7 X0 g# L) ]% q$ Q0 }
2 Z8 s6 B" x% M% S% G CDC? dc;1 L3 w! c' m3 R6 ]8 i4 i* u
$ j/ N6 S1 l8 ]: [. ~5 ]/ J) G2 D BOOL bShowLantern;3 p- X8 J5 {# |. I0 a, {6 J& V
5 u8 ~ J) s. k' F1 _: b7 R+ i5 L
………(系统自动生成部分). `0 T6 v" d3 B% b5 m
4 `5 d4 E6 t" D d. c& L
}; 2 J6 w$ c" T7 c3 Y6 \
. ?3 d3 @0 D0 }& U: f3 O 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
- g$ T0 e* Q: x$ J$ o0 D" T. o4 Q, ^8 S( G: F) q- {8 V* W
BOOL CWipeImageDlg::OnInitDialog()
B. `( g) m' P# {6 W, ? l9 [- T
{/ L4 x* }$ C1 O h
; g& @" ?; @. D. d8 n' c7 I1 K
………(系统自动生成部分)9 q+ O* D7 o d
+ t: n' c K* B- k k // TODO: Add extra initialization here
9 n3 d1 g3 |. ?
2 w! W$ [) e! @/ y# k //初始化全局成员变量
% z& X, c0 T% p' u' j" B- t" z. s* x" X) v+ n I O6 [% @
this->bShowLantern=TRUE;
' V6 D; A+ o; r
1 W7 N5 |+ e7 M+ F8 ~ m_bf.BlendOp = AC_SRC_OVER;9 [2 e, d+ z9 f# ?/ ~* c, D
?6 p% t* u" a) ]
m_bf.BlendFlags = 0;+ k6 I* V3 z( D {5 |
: k% S$ b& J3 L3 X5 H" W& N0 }5 x
m_bf.SourceConstantAlpha =10;. r. T4 S' J g2 T
: A0 J P* m) H
m_bf.AlphaFormat = 0; L9 P, }. e/ H# \; U H! o5 O
* t. p. Z' j9 M( E" ? //为节约篇幅,以下代码中略去对操作不成功的处理代码4 X2 V0 P# a6 [9 N7 ?. K% }# m
: Z/ y4 a3 v! Y2 E- m# \, b" d if(!cross.LoadBitmap(IDB_CROSS))
2 d& I4 U2 }: n D( X) r _( _4 L' E$ f L
{
$ u. H. P5 O0 S0 P# z
3 X0 y0 }+ j% ^, r% k7 K% h AfxMessageBox("装载位图出错!");
; M9 u0 K, T+ Z/ B. X! {) U
8 e* T3 e) I+ V' {. z0 W return FALSE;& u) t- d: U# l4 S* ?0 z5 C
# C4 V, C2 C% J3 h3 r5 _8 V+ H
}5 ~% B: K$ Z! b% s: e& `
( K" C: P$ s: l3 \ cross.GetBitmap(&&bmp);5 z2 h6 g2 w2 ^7 d/ l8 w- n
; h5 v( _$ W' W, p) e- z lantern.LoadBitmap(IDB_LANTERN);
: r/ ^/ E6 f L& W8 B; _/ F# B* o- r
cross.GetBitmap(&&bmp);& V7 ^. Z* s+ s4 ]6 F
, P, i+ n/ b x' c
//获得位图的大小信息# s9 V1 ~: `5 v1 u& r
" t* b% |7 T, ~1 v
bmpWidth=bmp.bmWidth;7 ?% A0 p, Q( W9 r
( E; }+ D# z) N* `2 X) a7 R2 z' Q bmpHeight=bmp.bmHeight;
' k; e, g7 Z% Y: W. r4 |
. X& {$ M* Y4 Y1 a dc=this->GetDC();
5 p' V0 c! f. {( v# U0 B
5 d, I+ A% i1 Y5 l/ }: K dcForCross.CreateCompatibleDC(dc);) p% l' N/ Z% o# D! D0 ?2 P( n) D! t
0 \$ |# s) ` w7 j- F7 a7 H% V0 o
dcForLantern.CreateCompatibleDC(dc);
5 P* h4 h/ `) V! A) A3 g" I, S& j/ E; T0 f
//将位图装入设备环境句柄
2 U2 e0 w+ c( ?& G- _2 o
( f' k2 I" l( U8 F dcForCross.SelectObject(&&cross);% l& q# m7 A/ f3 `7 \
2 r- x2 `) g" e5 p, _( V- S$ o. M dcForLantern.SelectObject(&&lantern);
* F0 Q2 r3 [3 j" t4 M3 x
" B" [% Z/ H _ //打开计时器$ o0 W: x0 P: W1 w; r
! i, w7 }" W6 J, h/ K
SetTimer(1000,50,NULL);
# b/ n8 c! h6 z
5 P+ r$ t8 R1 ^" ? return TRUE; 7 C I) m5 q# X: F" m$ A+ j5 X
" t1 r9 H) \$ k; S
}1 J5 I5 D' a$ i( V
# G3 U$ s9 m2 s7 Q6 O9 v% p. ` void CWipeImageDlg::OnTimer(UINT nIDEvent)
% B. c2 y% ?% r$ L) l, Y( o- Y/ r
{& A+ j) i# i0 k% m& g6 o. J
: `. I5 A. i; f7 S* O //图片透明度每次递增5点
3 s' Y0 B b. J& z/ j
A/ }6 h1 u9 m7 z9 y: s m_bf.SourceConstantAlpha+=5;
/ Q$ U6 |- v5 n3 U6 |2 p4 [" j- ?2 R2 w$ ^
//当第一幅图片完全可见之后,显示另一张图片
9 v/ b& e) B1 C% ~7 q f% _5 L. [+ F" J4 c
if(m_bf.SourceConstantAlpha>=200)6 V3 ?+ C( p6 h
2 n9 B/ U+ Y0 ~& L/ y {" y3 O7 {; j; z" k! `
5 z, S! h* Z! O q1 @* L4 l `
m_bf.SourceConstantAlpha=10;
5 u+ ?5 n8 a# w0 i
) Q- }2 z) Y3 Z) I //将bShowLantern做为显示标志,确认应该显示哪一张图片
9 S" S t- j" s
- E9 N9 d. E6 d5 [ bShowLantern=!bShowLantern;
2 o$ {% t+ t9 e
B; ~# l+ f) _ }
. B& a1 s; r( |. j3 a! P6 I1 u) n9 ~2 b* h8 c5 h( i2 ~/ O" M
if(bShowLantern)
' s" E) i. B) H- c- q/ d Y# x% K8 Z+ M( c8 ]2 [$ l, u
{
* P. }( F s! c7 n6 x( j
, \& V9 u! n. a# g( f9 N //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);3 s4 c$ i* d5 N* F1 v. E
6 D! c% }5 a& g, [) M
}
# \$ p- [& |; `
f+ O% O+ D0 _) ^ else
( Q% A7 j2 G5 |% h/ Y
, O& P4 C( n- o3 k* H {
8 l% C v Z; X
5 B' R) U# O. a( A+ c //按透明度递增的方式显示“背景”图片$ B9 s+ F4 t' G5 O
- F, m0 t" F& g3 t: r0 _
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);$ p8 Q* m2 |" }0 x4 `# `
4 t( w* O& {' V7 M4 P }
. {% _" u8 d8 D! q7 [: {
- m* t( K1 t% a- @) E' {7 r CDialog::OnTimer(nIDEvent);
( F7 Y6 L7 M) g0 j; S: T6 \! ]; m, G: F
} h% Z! f! b( K) ]
8 h+ C7 ]) j7 N1 H0 v
. n2 c' S7 @6 A' {- Y* ` 编译说明4 }4 [0 G9 X2 e$ R
) x4 R) r8 Q( z
, A3 F9 F4 H* `+ g2 o7 e9 y6 x 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|