|
|
周鸣扬
* T. O" @) @8 m6 e" j8 w4 E) W# ]% i8 [ Z( r+ G
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
$ k& P4 b j4 O% P5 O) N
. O' w1 n7 \1 C; m
w7 s8 {1 ^7 k7 D1 I 解决方案% o7 |! [' M w5 h: y/ a8 ]
i7 [; b8 I! A: n) B z4 b7 b4 D0 g
- k+ Z8 z; W7 o2 H5 L6 X: H6 D7 N
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:6 i* ? ~7 e8 x: D/ l/ f' O4 F
" G K8 Q$ t( G5 q$ f
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red & `% x& V" C: _8 A @2 s' O
7 t# }1 g+ [( @' C% ]8 f3 P Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
, @/ C0 M5 E, E& t6 B- N5 \/ \, U. ?; `- n8 @7 i3 H& L, P9 h
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue6 T* P5 b& ~" d7 n
# ~2 |9 i/ P! w0 r( s1 C) j 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。* O; G, E4 I$ o" b* v. l( t
, K: F; X: ~/ N8 j+ B* s
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:6 T1 v3 b" q0 b- O
# V D1 ]7 E2 B- `$ l, U BOOL AlphaBlend(4 V3 s/ y4 V' k, p. N# X' g8 b
- _- t% S, V+ Y$ |# R0 s' s4 x
HDC hdcDest,
9 T- o- v+ |( R& ~6 ?0 c* h' [2 e. c( J$ p3 L" y
// 目标设备环境句柄
) y' L! D0 [0 O" x- d: ^' q- o8 N3 P, K+ A
int nXOriginDest, ( h1 b, Y5 t2 {, m
: B! x* P# A7 M6 ]: Z. L/ u
// 目标坐标x M: g) A9 i8 R$ `& }0 n! V
' }. R) h0 v% Y; D; o5 m! m2 q) ] int nYOriginDest, . g L$ V0 i5 Q C( j7 {
+ }9 H* E$ E7 u% d9 J& M
// 目标坐标y
0 c8 j+ g8 Y! F l+ i% f
8 D5 O% s0 y( p6 b$ j int nWidthDest, 1 u8 E. Z. R: `
1 x: U/ p1 O0 s% i6 ~/ K- }6 X
// 目标宽度
9 n+ `% C/ l, I' m* G2 A8 J& s8 k- _) X7 E$ a" B( r
int nHeightDest,
, r2 U, g& |: p- W" K$ e) z% w% n' f- S2 _( q8 k q, [( C
//目标高度; C; S' n! l; Q1 s; l5 }) Q4 ^
3 b, w2 c& k* _! v
HDC hdcSrc, 5 g, S+ f, u2 J1 B7 O& G
9 l$ T+ e: h# r4 l; r. S //源设备环境句柄
- w5 T8 u; s. S! f4 Z: ]
' ?+ p) ^ o! a. z int nXOriginSrc,
3 A. g# F' J# [" g. o' |$ g! d9 X4 z4 \" C: ?2 o0 |
// 源坐标x2 o7 c/ ^5 p( c5 s0 a% d; C
/ Z* F. x$ y( P/ J! [& Y int nYOriginSrc,
/ f- `4 f1 a# G2 I6 i
0 ~ }" {) d) A9 L+ R! N // 源坐标y
! x% m5 s' h- P' j. f6 b
@6 G# r! }) k8 J- I( [) Y* ~ int nWidthSrc,
7 v. w' ~! j; h0 }, H: u& l3 W0 F2 v2 y' q
//源宽度0 r; z0 [- v0 Z0 q3 E' e$ v( o) ^
4 \3 E% O8 R6 }
int nHeightSrc,
0 u, m( Q; `. p1 {, g7 }
* i0 p* Y3 @9 g4 O' o4 \; v- R //源高度
" o! V7 R, E: K& |* u. P8 |- l# r U- v6 C) p8 K" C
BLENDFUNCTION blendFunction
9 [) L, F# D% m* \" K9 r
% ]& `1 p6 H* w, h$ D' k i3 i // 合成方式具体数据结构3 \0 `8 K" F/ K8 D
4 E9 U ]7 k- M+ h$ K' Y' s7 L1 @
);
, N9 u& j8 j% L1 o
+ x8 @* Y( a1 \7 k- e7 V& @& B' t3 l BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:* U. Q5 B4 Z% O* R) U) B) {5 z2 i. ^
1 f" x! U7 h/ J# F
typedef struct _BLENDFUNCTION {2 X5 U+ c0 e2 U c! x
! Q6 r+ c- Z# c. Z: k2 i% } BYTE BlendOp;
1 e+ [. W3 B% G9 |! D0 ]- S+ E2 Q- o0 J/ h& V, M) k: }( T
BYTE BlendFlags;//必须为零
2 z5 J. I! @% p' K, B# O' C2 f) T/ x" ?- h% u9 H9 T, d# W6 ^! c( S
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示7 V" m3 }7 @+ l5 X! B! V
: l \5 E9 {; Y
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
/ R. l1 _& b) `/ _" Y: g; B
* X" Z" S* J- e9 R }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
% T( y! |0 `# {7 c2 A+ y. z' j* i+ f; S" Z
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。$ e; e' M9 Q% `4 g
# ?7 g/ j3 d' e" U
- \7 c. n) G4 v. g
编程实现8 N. n* n6 Z' }) {) P! p0 ~# v" z/ r
" E; e+ m8 U1 G% C
0 t* C& I' s8 U6 g; I# M3 t$ u
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。: Y3 c" l/ F: r% e/ g5 L) M2 m
" n; N( S( w: T- y( i 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:( Q: Z( o J6 t6 J
4 k8 ?& [. A/ A5 t$ m6 n class CWipeImageDlg : public CDialog3 X8 [2 H: ^8 Z6 H) @' h$ `
/ m) z2 |& x( \, d
{; T0 P t# ?* \1 x5 }
; v" X' B& u) ^: ]. [3 f
// Construction1 s$ }* Q- s) Z$ k* a" n
5 u4 m, R: S! w' F* f0 u: E
public:7 ^+ L4 A2 k2 I$ h" y2 I) m3 i8 C
% _$ @, l: n) X; I1 {6 M1 t1 F BLENDFUNCTION m_bf;4 ?" f3 s, l2 H, H* q. j: x( c
( v- \, }. p3 w1 B1 t CBitmap cross,lantern;% }1 h. [) I9 a3 _ P
* h8 d# S8 Y) D- `: _ BITMAP bmp;1 H* j2 g: A$ @
5 B6 V: `) \: ^5 i9 e" e( I* X
int bmpWidth,bmpHeight; v$ G. b0 Q# t* n. B+ s6 {
/ h; F. g* E0 C8 f* E& u+ @ CDC dcForCross,dcForLantern;
, u2 H$ \7 s2 x( `1 ]7 I: s( U' e+ K$ t
CDC? dc;
3 J! O' p' {* j5 C3 U' b; s: S
! I# b* p! ]- r: H BOOL bShowLantern;
3 z' Z- V+ Q j: P0 a! [ U" D; x- Y0 F. ]* @
………(系统自动生成部分)
% o( W7 ~9 M6 u# k* n8 u
) B3 D8 K$ ^/ _9 k0 ? }; 3 t" J$ B9 V7 }/ v
! R( Z0 G# ~# h- P$ G# ~+ J2 t3 Y s 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:# `$ C$ Q7 Y: K N8 b
3 k' `# s6 X& h1 I* R BOOL CWipeImageDlg::OnInitDialog() l0 q9 z8 R; c6 z
* Z# t' _$ u; `( `" K$ S0 C {7 A* Z: @. J0 a
|6 P: B6 b+ M2 h$ L! K5 l/ k$ d ………(系统自动生成部分)' S1 H$ E5 j) ?
( p& I: @3 \+ X! k
// TODO: Add extra initialization here
3 {8 e1 x4 r4 V; {3 G2 O3 m" x7 A, w# F/ S9 C/ N7 e
//初始化全局成员变量1 [6 Y1 D- B( |" b4 c
7 g& X" }" r; z! k% z2 T+ K9 r
this->bShowLantern=TRUE;9 t1 ~$ g5 L1 J) c) S' x2 t
: e6 n3 ^! e6 s" q
m_bf.BlendOp = AC_SRC_OVER;
- B# J/ g( d3 ^" U7 f
& A7 ?8 t i! y. t2 _ m_bf.BlendFlags = 0;9 i9 t) J* A y6 X4 c
, j" _( S8 p3 R6 c5 s6 _
m_bf.SourceConstantAlpha =10;
4 C" x' @: o: u/ @" J5 x7 D
8 K) O$ z( [) T a' z. G$ n m_bf.AlphaFormat = 0;
7 v# q9 q/ G7 ~, s5 o# V7 U G
1 S7 \" z; J8 E! J P9 H //为节约篇幅,以下代码中略去对操作不成功的处理代码7 _( Z. T- ?$ ^- ?. I# g3 C
# I4 ]1 ^9 N4 m% T# }" p
if(!cross.LoadBitmap(IDB_CROSS))9 M9 v7 ]: t& |
" O" }' }$ t X* |
{
; z9 h) {3 ~- P$ ^9 S* c! B! e* s4 l6 |3 {1 W) u, _
AfxMessageBox("装载位图出错!");
7 b" Q/ z7 n9 X0 M& l$ M2 K& I: ]$ Q/ K) R% o
return FALSE;% O8 P+ v0 `6 B+ J
: }6 Z6 c# p% [' ?- V$ `, S5 N8 k }* l$ _1 u& u5 ?' ]* A; X F
9 x' Z7 A. e+ R& l* a cross.GetBitmap(&&bmp);
3 x; b6 S) f; V7 n) C# j
h6 H1 t9 c3 D9 [" ] lantern.LoadBitmap(IDB_LANTERN);: j) B( S1 h/ z$ c! d9 q. {0 j
* L$ y2 F2 U/ T! s+ q cross.GetBitmap(&&bmp);0 |( x, H- X; X' B) L! \2 D
- z% f! @) l$ a
//获得位图的大小信息
3 P, i3 E- ]% r$ c3 W* v {% s* d0 u0 u8 |( F8 H( { ^. H. A
bmpWidth=bmp.bmWidth;
3 E* Z# @6 y! \7 D+ |! |$ V3 V+ E/ f% {! }- L
bmpHeight=bmp.bmHeight;
: u* E) K. r4 t. H a3 M* {# f ?+ S- b# Z- [( V
dc=this->GetDC();
/ v e# [( `: e' o/ c+ T# v0 t% {" n- d8 D5 u0 j
dcForCross.CreateCompatibleDC(dc);9 h( t- t+ ]7 W' m
7 Y8 H8 y& J' L+ s6 A+ J2 `
dcForLantern.CreateCompatibleDC(dc);
# a/ M. f/ v$ N+ q+ C1 D5 p Y& I, f( u1 h U, n
//将位图装入设备环境句柄, q% u7 C& z: y; p
, B( p( w0 u5 v5 L
dcForCross.SelectObject(&&cross);9 x% G1 y7 R' G2 y/ B s
+ Y# L$ }0 L; h( W1 s% d dcForLantern.SelectObject(&&lantern);% u; V2 ]7 C0 M- l9 P$ j q/ ?
) @0 L! E2 E& p7 F8 G( B //打开计时器
/ u$ E8 m+ @8 M6 B9 W; s! z; X3 }9 @/ p4 P" H* `. @
SetTimer(1000,50,NULL);/ Y: Q4 O9 {) A7 l: M# g( m
! z4 ]/ P& o& h! o' x7 g return TRUE; 4 D$ J( J$ _. g. {& o- \. K
; l! X1 S, [% L! p }
/ d/ J1 j% {" Y4 A# `. `& Z# O1 d8 i5 ]' K2 W' O
void CWipeImageDlg::OnTimer(UINT nIDEvent)
% g0 i* k" H5 ?" ?! g+ I9 X6 m2 T& w& y$ u# O% u' e
{
/ F R4 \5 \& h+ k; O9 H3 i1 N' D4 y! u, S- S6 k% {
//图片透明度每次递增5点
* p) s+ K4 z ?# G$ [ f, h# `0 q/ t/ v& \0 ?, k3 n0 w
m_bf.SourceConstantAlpha+=5;3 w, A9 w, @% ~! W& y( Z0 V( `! Y/ O; E
! u5 o- ]( u! l. }/ P' I$ N
//当第一幅图片完全可见之后,显示另一张图片' C: E! d1 v. W( \
4 @1 C7 Z/ R. Q% b9 F$ N+ D' _1 K# L
if(m_bf.SourceConstantAlpha>=200)
2 R& A4 J' U+ j% u
2 _; {: R, u; K2 e8 Z: F2 n {4 w. f! s$ |) O9 @9 o
' T& [' b8 _. h4 D. s! i1 M5 U$ J
m_bf.SourceConstantAlpha=10;9 d0 q, U& Y# f! d8 t0 m r
. i! c* @* Y$ v c$ N* J //将bShowLantern做为显示标志,确认应该显示哪一张图片 1 x# m. H2 Z# C7 y
, @1 S- ^7 Z# \& ~9 ? bShowLantern=!bShowLantern;/ M: `1 ?2 _ C4 k" |
1 b+ Z$ q) G& c) I0 h }
5 c ~, O( p' M$ j9 Z6 B9 j5 ~/ r4 L
if(bShowLantern)
* A$ {* U2 D7 v/ b5 m7 O+ a7 m$ }* X% p6 v
{/ I* F4 s. }, L, n& O
6 e" s/ X S2 ~- E5 B7 V! R. Y
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
' I6 A2 L0 R3 B: A. I: S) J- O! G9 x% m. D
}
; i7 y" {$ f2 Q$ I- F9 v0 W6 v
" m2 o" w! `. C% r! D% b9 N else c4 i9 `* {, ]1 [
+ @+ X; k* S5 H' w% ~, p
{* \2 c3 Z; g; D$ a
, w( g2 _$ f p& P7 ] //按透明度递增的方式显示“背景”图片( W/ c3 q" {& v. P4 _
* h2 a: ?; T2 h) y! U$ d3 l AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);* V7 u1 E& c- Y
- \6 F: n( p* F2 _8 P$ Z
}
: R Q6 K( _( i4 K1 B, r) \7 _' L$ c" H5 q. R, w$ }
CDialog::OnTimer(nIDEvent);+ i5 p( `" h3 N- r: _$ a/ v- k
' Z- t t k! v& o
}
! N0 Q7 t% V& m2 @# }9 H
5 b* z8 R' j( h. W/ s0 F/ B: ^/ z- n7 ]9 y% B% `
编译说明* k; a! f9 N& K9 X6 }, x1 _0 _
& Z; C0 R5 d: _( K# x7 E) z4 i1 R1 }: T7 p
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|