|
|
周鸣扬
1 w6 `/ h m( ^
3 T/ H4 h! w3 G 图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?8 P7 k3 p. S, V* c* Q$ l
! M+ I. M+ \. l4 A. \9 n3 f, }1 e
4 Q4 T6 y1 X8 @6 R3 h 解决方案
* r* x% t2 ]6 T# X3 h* w" ~/ Q% T. ]1 |5 U8 n7 r/ `4 l$ C
# {7 l- z" f; Z/ P1 z$ J 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:- _% M$ _ y# ?1 [; f
! F+ Q1 l/ U3 b Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red , q! z" [/ M+ l8 ?* H9 n
! y) b9 l5 \$ J
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
p; t4 w5 o9 z, G j K! n6 n" O1 |+ n
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
8 O" x* }4 v( \1 ]& ?0 z3 W# W; R, l; ]4 w# ~
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。* V3 l- ^# E9 F% i2 Q: i1 k
$ U. i1 _$ c1 C7 F; D, q' J% Z
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
- _/ }* c7 b+ Q- e* Q3 P+ ~: V9 z6 A& B5 _' h8 h
BOOL AlphaBlend(
7 Y9 d$ Q4 P3 m) x) [
$ s2 J, b; s" i" M* _# h! w HDC hdcDest,
2 e: ?1 l- F! g
. y1 B' y5 T5 I! B2 \4 f' x // 目标设备环境句柄
; D) ^; d0 ^+ b! k! Z
) L3 t B1 f3 G. y( V$ I int nXOriginDest,
* n c$ O- F' X
+ T7 \# V; j9 S7 ` // 目标坐标x. W0 | s% H* W
& E5 D; A6 \! c! w
int nYOriginDest, ) w! p: s, l" J9 H& x
2 b9 `: h' R, G( D8 o+ z // 目标坐标y
8 k$ s; q. f* F; u! S8 y# l8 A& X
int nWidthDest,
2 N3 H6 ~3 E8 T9 U8 y# C4 Z1 V3 j5 C1 ]2 n7 Q. Q* p$ k+ j( a
// 目标宽度; C3 L6 i8 s+ l7 a
3 ]- {9 L& E: t/ H8 B+ x6 E7 k+ }. T int nHeightDest, 4 d: x) @" Z) P: l
. j% [+ ?: T: Q
//目标高度
+ j6 i0 ^( x( }5 a8 P8 j5 l, d0 S; i
|9 b& Y' c! y HDC hdcSrc,
; v( E/ s$ F( u7 L; k+ d
4 D' f) Y) W8 z" [% Y1 i& z0 A //源设备环境句柄
I, s! ]1 v3 [/ j( ^2 k( F- i; c; M
! S; q4 l1 e& M8 o, O$ L int nXOriginSrc,
+ Z: K$ B- S6 K9 K9 h9 u+ d
$ l4 b4 j& W+ B3 H2 O // 源坐标x
# Z; } P% g c+ _5 J, M& B$ g$ l. ^& c" ~. M
int nYOriginSrc, 7 Z. w6 L& l7 Z' S6 B4 X* |
: ]$ o% ?+ p+ s: J5 q // 源坐标y0 e+ q+ g* U- Y' ~
+ ~4 F* [8 p! A
int nWidthSrc,
7 U$ z! z8 G* z2 ?* ]
7 Z& D2 }. I3 w0 h( u //源宽度
8 B* m: I$ v: P0 s' z$ B) B; f% q& o u' q
int nHeightSrc, ! S, {: y+ t* | `& S& E
2 S% U. R' S2 F L+ [- o9 M
//源高度
0 D, a! K8 E: s' D3 a9 L) ]4 a, I. s6 T5 G) N
BLENDFUNCTION blendFunction $ W3 e( V# ]4 G$ |7 ~5 _
8 U8 ^ p* k8 C0 J/ m
// 合成方式具体数据结构0 Y9 x+ N4 l( w5 f# ^7 ?$ v3 ]
! u d! ~3 `4 D3 w! M
);$ X& ~3 d% v& {5 p7 F
! Y3 H% Z P* E. I
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:: `0 b+ D8 }, B0 G! \
; }) X' ?4 V$ w4 u1 D. S0 G
typedef struct _BLENDFUNCTION {+ E# ~2 D3 G" x2 T2 F3 q; p
8 k- Z, d7 s# B0 n& v/ i7 G
BYTE BlendOp;! o# }3 f& E2 r, M3 _& S$ }
5 U: a. r [5 u' x! e2 \& Z BYTE BlendFlags;//必须为零4 S$ H: ?) x# R* @5 d( T2 u
( }+ |* v, R$ R6 p; ^/ e* j0 @ BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示9 I2 Z. s7 z7 I! I4 v2 m
3 ]1 a) W, ]1 @- Y d( j BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
7 V/ u! [! [+ T% K( G$ E0 g0 b- A. U5 `+ r
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;$ n: m" s9 K% o
8 L+ |: m; w' U
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
% d* Y$ f* Z8 s1 t _- o$ n; @. ?7 `' R3 y! P; D7 c2 H+ p
/ y* P) w+ ~' U) z1 h5 G N 编程实现
% N6 m% p/ A' l, t
- {# ^' m6 ~* L/ j r$ C7 A
w7 d) z( ~6 }7 A 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。1 r/ Q6 Y$ y* H# J. E5 [8 ]7 H* U
( e8 a x, Q9 B) x1 p 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
9 k/ C/ ?9 [; l& N$ {- A6 R& Q
/ _8 i, Q" H4 y" w6 b class CWipeImageDlg : public CDialog2 Q/ c5 d! B' ]- ]7 O* g
( W$ r" b" S, |) D {; d6 n, P& ?( I7 @# v
$ I: o$ G% H. V/ d2 @ // Construction" W' ~' j8 _9 ?/ ]) f9 |
1 z) [" |! p3 L2 u4 z: v9 i
public:
. y+ N. ?$ ~' t. y ~( p& K" e; ?* n! E8 S5 o! F
BLENDFUNCTION m_bf;. c% X# V4 f7 U/ K' b2 r# V! p4 ]
. E: B$ {! z( {$ a; z) j
CBitmap cross,lantern;
1 Q* q. ~* L2 }8 B1 U$ W+ s& _9 @7 g& b" u) |: T/ p
BITMAP bmp;
4 Q+ `' G6 Z2 W# q d
) X* C J/ L, p, J \( Q int bmpWidth,bmpHeight;
& V9 L: A) N2 b5 P3 c! l9 A9 F6 X8 I" z& \
CDC dcForCross,dcForLantern;) \ ~: @! r& I3 {( O- i
- m8 \$ y) f3 r- d% z' L CDC? dc;4 N! v0 G4 Y7 k/ t
. \/ |$ S5 N c& V& n BOOL bShowLantern;4 ~+ j1 S. {3 l) r8 e, f V4 P
) p& W5 L1 {0 n! B
………(系统自动生成部分)/ w( u4 m0 S" Y8 m4 {# M
, T" @3 m& y3 [. D
};
' k) [/ a+ p7 n: w( V4 W4 d& U6 ^
& K# g9 L: _1 o4 O' Y 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
9 c# T+ I# q' Q( Q9 J# s
$ I9 h* s1 `' P+ u BOOL CWipeImageDlg::OnInitDialog()
9 s6 w( [$ U, B+ C W, a: a/ V+ R) n0 T; ?
{
) H Y: _0 i! z* U6 e3 k
$ w( A- R' r" Z0 F9 I8 p ………(系统自动生成部分)
" @ s3 M& B+ m5 w3 j1 T2 s+ i
9 O' }; I4 H, Y; J // TODO: Add extra initialization here
( ?! O1 X* c5 Q }/ _& N, \7 q, V% M! o* L
//初始化全局成员变量
; C7 c+ w8 w% |5 A) K, ]! N; g5 P. T* h3 t$ @
this->bShowLantern=TRUE;8 {+ r& d) E0 \( _) ` ~% [
* B* s' o" {3 }' W, Q- g6 }# i m_bf.BlendOp = AC_SRC_OVER;
: L$ A( q& o: k/ H2 h9 ]4 U& ^8 ?3 }! _
m_bf.BlendFlags = 0;
* b3 k. f+ D4 e# Y: d# d9 W5 F8 P) p/ M0 b- I- ^
m_bf.SourceConstantAlpha =10;* w) u6 x a- L2 E; I" S
4 x- ?, }8 S5 J( e6 j3 z m_bf.AlphaFormat = 0;
! ^9 j; H/ u6 q! Q* t" J4 d3 ?5 [" l9 [! q: D! b
//为节约篇幅,以下代码中略去对操作不成功的处理代码. T- m, V- }3 W5 Y' j
% ^8 {+ p" l( i if(!cross.LoadBitmap(IDB_CROSS))3 m) R; C) ?) a6 m7 s3 \1 @
. F( O) D- Z. a2 i' i/ s' S {
N+ ~, v; \2 r! Z# _
- m: Q8 }& r( J' }4 z/ H, d AfxMessageBox("装载位图出错!");3 O0 w4 a, w/ N) j. ]
% J1 k6 d! o& K6 d3 } return FALSE;
* j0 }- O% h/ c" |# |. k- j* y6 a6 ~6 ?
}
: ^2 ]% c3 a# r7 S# j+ D% m; M3 @+ e& x8 j0 I" L0 G8 M: e( h6 ?9 U7 {
cross.GetBitmap(&&bmp);8 |) q8 N8 p7 I" g- O
, x- j8 w& h9 b- f3 Z5 h
lantern.LoadBitmap(IDB_LANTERN);
8 a7 a4 s7 g* e7 j4 f& g( {9 j) o, @8 H m
cross.GetBitmap(&&bmp);$ a5 [/ z, g8 T1 e2 N
4 q9 I$ J6 b0 f! Z5 n9 Z //获得位图的大小信息
; Z' r" M3 Y6 `5 R+ ]
; C- T- A& G {1 n+ r5 k3 @ bmpWidth=bmp.bmWidth;
% `% L' g/ H: I
, l, B; d; e$ n! {0 t! f4 g) ?9 z bmpHeight=bmp.bmHeight;
5 E: X7 U' @. z9 M& T
9 E1 m; }$ A9 D* _ dc=this->GetDC();, V! Q( W" P1 w: x7 T O
3 M' \- Z! ?# m4 Y% _9 a; W, \. n dcForCross.CreateCompatibleDC(dc);
0 ~9 C& G$ c5 n1 f' N( C1 N2 ~
( ^7 X O8 m" _" M6 `+ K dcForLantern.CreateCompatibleDC(dc);+ s7 }0 y* V' K
% y0 B4 [6 q( V6 N6 c //将位图装入设备环境句柄1 I% w7 k5 @/ k3 T1 ?+ c
, U" e6 Z* ]7 s& v9 j dcForCross.SelectObject(&&cross);. f4 d) ^, `# p! ?! X
& K# O/ [2 P6 ^# e
dcForLantern.SelectObject(&&lantern);) ?, i r' t& f* Q
1 p/ l7 c: a7 m6 B! Y
//打开计时器
7 q/ I/ K3 ~3 ~5 x. s/ t; [3 N" p% L9 P# |2 m/ i' S
SetTimer(1000,50,NULL);
5 c2 _; C8 [: R; |! i7 o% Y! S/ d8 \/ l
return TRUE;
8 M5 [4 p' L+ y( @5 z4 U) D+ Q; `+ v2 m
}1 }/ y. e" j( D
- F# I& b9 r: @) a) z2 V void CWipeImageDlg::OnTimer(UINT nIDEvent)
6 ^* ~, M! {4 N, O
' L/ I: V* w# q) j {
1 d7 |% y- V( a0 S& _
' s9 E+ Q2 v& n; m8 E //图片透明度每次递增5点
6 N/ [! f0 h& a# d! f& j, o( p% R* k: w0 Y
m_bf.SourceConstantAlpha+=5;9 n8 G7 k i# @6 h1 @1 i
% B" c5 g, T( x' Z
//当第一幅图片完全可见之后,显示另一张图片/ A- U" M4 e" V. t2 b: D
7 M0 a2 C7 V0 `1 R3 f* d+ \9 X* O' `
if(m_bf.SourceConstantAlpha>=200)
7 Q" ]5 }- S& w* e: Q% C! y! R
$ d3 D4 C- J: U3 D {
J0 l6 E% j `' u. b
1 ~8 |4 ^6 g" I2 g m_bf.SourceConstantAlpha=10;
2 _5 j" D4 s$ ~# w! E u, I. X0 U* c% [
//将bShowLantern做为显示标志,确认应该显示哪一张图片
2 Y( n% b) ?: {7 X' g* l3 o
+ }; s( Y1 g+ ^' j bShowLantern=!bShowLantern;
# m5 N9 v# k; L! l: P' [, W) E6 {, ~/ b
} {+ E5 A2 m% {; R6 t2 L/ P8 K
$ s ]! e- W5 E/ g/ T5 \9 ? if(bShowLantern)1 _6 \- ?: z1 ?# s2 k
- i; d5 F6 `) F! y* D3 e8 _
{
. v! I# a# I: G9 H# k/ L" X) M3 Q1 t" Q- D3 N; M, p3 u$ q
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);1 E$ }9 W% i7 A
9 h- a0 c. x. Z1 T; a x6 X
}
9 q1 J H* Q2 f) Q8 w9 q1 T; a8 t# @5 ]
else1 O3 A! o- G6 n3 k# r$ Y& b
# J0 N( I4 w( U2 R0 B
{
, x' G0 |8 o( @/ k5 d5 V ^2 Z2 n9 l8 R" X' T' x8 @; C$ D
//按透明度递增的方式显示“背景”图片
- W, K5 Q F: F% L4 y$ z' j
) E( [0 h( f* C% { AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
1 V3 R3 W4 G' R. h0 {* _7 \+ ?2 a
}
# N% t, ^! X7 ^* H# Q2 ?' {
1 P k* Q1 v1 C5 F' `. @3 a8 ` CDialog::OnTimer(nIDEvent);
: T% i3 @+ D+ J2 y
% E6 E/ e/ q1 t2 P7 f$ i }+ y) J% q" F. [5 X; \- [- h' C5 T
+ }9 V8 g2 x H: |! l" P0 X% j0 u
) c9 n) d# q$ _9 ^) E1 E7 R 编译说明- a% B5 b, @3 h
7 O! [5 J" z5 v: X, x+ x! t$ i* G
6 E) _: R& y E5 [8 `5 H9 D 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|