|
|
周鸣扬
& H# ~* {, R @6 I, W9 ]6 ?9 e. @9 o0 p% e! E2 q+ g
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?4 A/ q) \% R u+ Z
. ~! U# Z4 J4 ?9 u, ^, i) F Q ?+ m; Q8 R) H5 m t9 ~' u
解决方案3 [6 X( l- J& w; r7 K7 y
) y8 }: n; H) {# T5 u3 O1 K
- b4 e3 a; D; ~9 x 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:+ \ [& [1 Z/ W- q2 y
3 R$ V+ L$ ~# J% m
Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
# F% L. H' F3 F" a2 v9 n( S# {0 p4 r8 P8 M- y: O
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
! e9 M3 i" x* M0 W; U7 \7 U6 P7 u! Y3 z
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
0 d8 j" X1 t8 S: w
# X% @* t/ c4 \# M; w2 h# U 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。+ D, x* }# \: [( R$ R( o- n+ _$ ]
) O! s( t: s4 m* W- m3 {! r 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:2 n" y" J8 r8 F3 x8 }- @$ T+ O" D
- b9 K9 k# C N6 E1 d. @( i
BOOL AlphaBlend(+ X% Z- r# C1 @+ }" g
6 i, |+ s: w P& n7 {% k HDC hdcDest,
, N8 B5 s. n8 @% N( l
0 K7 w4 e! ]. S8 Q9 E M // 目标设备环境句柄5 @5 {9 E# \" e' x4 j9 I' R
7 Q$ x( Z! O' B* A5 f3 J int nXOriginDest, $ o8 K: p; R6 g, P/ U' ~) f
1 M3 H+ t- I/ l/ X
// 目标坐标x7 v, h5 h0 u! O
2 [+ ]$ @( o9 b2 D2 Q
int nYOriginDest, # A* R( d# c! {" G) P/ j. T0 O
. S; K; x, o2 @. \5 G a
// 目标坐标y. w( {/ j: q8 ?
& k! c7 T. ?2 Z6 J int nWidthDest, ; n- U% M$ d# }2 R3 N1 u! V, ~
: P+ i+ s2 v6 r" g+ v5 l9 c: m // 目标宽度5 g: P2 q7 B/ G1 Y5 g3 w V
A2 n, [$ U* }" U4 V' U+ e7 A int nHeightDest,
+ y; }* B# Z* _" P
7 L5 K- t4 I5 c0 j //目标高度' A5 r* i! N" V: Y. h
% j+ X/ k, T# c8 z HDC hdcSrc, : W* }- B9 Q1 B# B z
6 q9 ^% I8 I) t% n! ` //源设备环境句柄
/ ?* ]9 p) T9 d7 D) D. O5 z+ t; S; B% F1 o
int nXOriginSrc, 9 ^0 r* g, X0 s: i1 ]7 z W% U
7 B& O# k0 z( U
// 源坐标x
+ r$ ^0 F0 k' A4 }1 a5 A5 g
; z# f7 q6 X' @& c; V int nYOriginSrc, , w$ z) Z2 S! Z) ?
4 Z" G0 j' H6 M" T0 R' @, b // 源坐标y9 n* n+ Y# e& L% N& j
+ |) |0 X* ?7 V/ E int nWidthSrc, @! Y0 A, X9 g! x" M0 o
: g( \# B4 ?' w+ ~ //源宽度
. r) o3 [# y* T5 A, b4 E5 W% A+ r& C' k0 e
int nHeightSrc,
, h% `% n5 v% T# V+ o3 _& @* j" d0 Z) r% t
//源高度
* {& m1 r- i0 U5 ]: X$ D7 e& e' }# f4 _
BLENDFUNCTION blendFunction
0 f6 V; s1 F- [. m& y. \
6 W+ \/ o: P& n, L: K, c9 m3 x // 合成方式具体数据结构
" v# ?+ a1 v$ ]' o! X7 j( o
; ?# ~, ^$ J9 @" f- `0 B );
6 D4 K+ K2 Q* L# s* h" v
' B h( m/ M- A BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:4 g% \" b: Y1 y4 B+ z$ a
) y1 C. }" m, g3 ?* { typedef struct _BLENDFUNCTION {
: F: y4 k3 D0 W8 M) f/ I0 |3 `* b* U6 B6 M- {1 [( c
BYTE BlendOp;
; J7 R& a9 [, g' n% W) u8 k9 B% f% a& ~& _
BYTE BlendFlags;//必须为零
) J) Y& y( z1 m s2 _1 A$ I' g
0 Z: B/ a" }7 r$ @7 g& _ BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
8 G7 y- x, y, y
2 a: A/ n7 O! O& D' M BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA+ F" d7 m; T3 Z+ d
. K1 ~9 p) s0 B5 p8 T! j9 q) L+ S% n
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
& Z9 g6 ^2 D1 l9 S9 @, P# z- Y
1 m; l$ y4 u F: a& V3 X! a3 ^( F 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
4 I9 D& m6 V; E- t6 z4 e% u4 ?0 y* }; ]* y `2 `
3 w4 P2 H- a/ n3 Q* v6 V6 J 编程实现' V0 r6 W' X, A% n5 x" S4 E
}/ a0 X/ M) V
, Y# x6 X( y9 m! e; L 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
, n" {8 n+ @8 ]
% |/ G6 _! C! L4 D 首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:% w- f9 ?' E# |, N% c! x
" E, W; u: D$ \ A, q9 O; | class CWipeImageDlg : public CDialog8 Z" H# m! @0 J; e3 d
- p8 z5 p7 K$ |( @- ~ {8 Y2 p# a" d5 ~; ]1 m
: r$ L) Z# F- x0 q; L8 t // Construction
, b& v3 W" V. g' w T! x m6 x4 d8 D! j) m6 U" s
public:* n$ x5 V! C- r3 v0 s s
( p. S: e: ~% b4 v9 V# ]
BLENDFUNCTION m_bf;$ Z0 ~- U$ ?% Y3 ? T* ^8 M
0 g/ Q1 t. c7 N$ c' F) s- @
CBitmap cross,lantern;0 _5 `* z* T9 S% @' B5 m3 f$ k1 H
: o1 ]- `1 `, Z BITMAP bmp;0 Q* {7 w, [% r. H' E4 F$ X
" ^; ?. j3 \' s4 M# k
int bmpWidth,bmpHeight;* C$ T6 v% |% q; D' h5 b* P6 Y
* D. W5 M( X. k% _7 X CDC dcForCross,dcForLantern;1 S1 ^" I/ L( i" \$ g
% Z) F5 o* y4 y& c6 D5 `2 A' d CDC? dc;
- b( C) }8 A& N0 K" b: a: j7 I. J. K" X, T; l! i% r
BOOL bShowLantern;( G' y0 S$ F9 Q' x' w% S
) G% g( r: Q8 X0 O
………(系统自动生成部分)
) y5 T$ G" V q2 Y, D+ V3 \6 T; u1 {
}; - `1 z" C* {( V5 x& F; D
4 E( Y% ^* U5 [& ~6 u
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
: T; c2 Z4 ?. ^) m2 `& H o$ O& T1 T" f2 d+ [
BOOL CWipeImageDlg::OnInitDialog()
* W8 ]) f% r! w
5 l/ V9 k1 L) P6 ] {; Z K2 y1 `/ ?+ f9 [9 `
. M# D* x* w7 `, [( L+ {9 l
………(系统自动生成部分)) Q5 P" c m: ^2 A5 y% s1 e! e
! G% f t2 p% N' E& X+ w6 i+ V
// TODO: Add extra initialization here5 M6 \8 N2 \1 d6 ~
' C( e2 ~, u- a* Z/ P8 {4 ]
//初始化全局成员变量
" {% y% X. z, X$ N0 n0 o9 S/ w9 B4 J |" y( s* k; |
this->bShowLantern=TRUE;
6 }) B5 g8 p4 n! ^, W& u1 P9 X& {8 [6 |8 s4 U
m_bf.BlendOp = AC_SRC_OVER;, r) W6 z0 G6 Y. J& v$ t
4 Z4 E6 X) b/ T8 b2 } m_bf.BlendFlags = 0;# q0 Z& Z: ~2 a# t9 Z# M( C
. B& x$ O$ [6 G7 E5 u$ f
m_bf.SourceConstantAlpha =10;0 Q" G( g: t `
/ X* Z/ `2 X6 K* R m_bf.AlphaFormat = 0;9 I: h: m8 K: M1 o
. T4 k% V4 U$ A7 j5 p/ y% U //为节约篇幅,以下代码中略去对操作不成功的处理代码# z1 p- p) Z. b
2 {. T7 \3 A, w
if(!cross.LoadBitmap(IDB_CROSS))
/ z5 B9 o3 {3 O; T
0 ?# S, [; T. i) D. T {2 i) C9 _" @2 ?; I3 s' s
* A& ?; t9 ?2 }) h; b
AfxMessageBox("装载位图出错!");6 o9 _ V- I, b+ Z
! m, ?4 ^/ t6 h9 Z6 C+ U! d
return FALSE;. R& d: Q# T* i
) _# B8 e/ P2 M4 {
}
' q! r( d- w! a/ l& \& C' z2 X3 R
7 T h- T6 L3 j1 a7 ?8 N cross.GetBitmap(&&bmp);
, T6 ?0 A! M0 Z# j6 B6 J5 X" W: D
; M5 g7 J: J9 L8 m lantern.LoadBitmap(IDB_LANTERN);& }7 d5 m+ L9 Z" E! ~
5 z. n% z8 s. h3 R# s cross.GetBitmap(&&bmp);3 @' u4 E/ q b% W
N6 j9 I% W% i- {6 s- G% \ //获得位图的大小信息
6 F! [9 w5 M0 p$ P1 e3 u
3 Y5 H1 o3 F; f& C- Y) L bmpWidth=bmp.bmWidth;1 W" ^# V, R. k: }
/ \3 s/ K [( V" | bmpHeight=bmp.bmHeight;
8 S: U& N: |$ P6 a/ A) F! w
6 z7 ~ f( _3 U6 S2 f dc=this->GetDC();
/ O& Y2 g- i' f6 F3 w$ W$ [/ t% `
dcForCross.CreateCompatibleDC(dc);
7 K- c/ }. r6 ?" w
. V+ J7 V2 @ k dcForLantern.CreateCompatibleDC(dc);
6 `$ G% Z' i6 [
5 k B% U* y6 F //将位图装入设备环境句柄% T/ }9 A- o5 B: e O
6 Y8 t7 v% @7 J0 }
dcForCross.SelectObject(&&cross);" j5 E8 }/ r6 S& k- O X3 H5 m
* Y- N% l& z4 W& C8 U; B dcForLantern.SelectObject(&&lantern);
5 O3 \+ L+ E9 D( c
$ c! ~7 N) q+ j8 z2 r //打开计时器
8 _# @( I$ P/ j: B" E, i9 l! J8 c% P0 ]3 D: u/ N
SetTimer(1000,50,NULL);4 Q5 m/ H5 w; s$ k0 G& l
. ]' }( S1 ` P, q
return TRUE;
7 E m1 d! G. Z0 x E* S: i+ S: q( \+ f
}
- E$ E, J$ M. v3 V& G& a
9 [; {4 U8 ?3 l6 m void CWipeImageDlg::OnTimer(UINT nIDEvent)
( O' s5 k# ~8 J4 \( \! I2 j; |/ w! w0 k5 U& P
{
2 D/ [1 |7 ]5 w; h; E; h9 v' I! k2 O0 v, y8 n t+ C7 p
//图片透明度每次递增5点
; _! c8 z, C8 j7 o% T* s, M; M4 i; a
m_bf.SourceConstantAlpha+=5;
" H3 ]) ~9 s' k" @6 N: {4 b
& _# R( M8 F" I [7 R //当第一幅图片完全可见之后,显示另一张图片' T' V$ p5 z! H" I V$ R
3 l- I# n6 H' \7 ?0 M8 t- W( n
if(m_bf.SourceConstantAlpha>=200)9 l8 D, d, g' k. R6 ]- F
6 A% P( U9 ~& W, ?$ {7 Y4 F {
6 e: e$ T* G+ M* U/ U$ i, U- z- t! z) |+ \ x! h H3 r, J. h+ V
m_bf.SourceConstantAlpha=10;, W' e8 u" ^0 w
. `4 h( b( z& m; S; E& P1 v* ^
//将bShowLantern做为显示标志,确认应该显示哪一张图片 - T5 H9 Q5 \. e
! h3 r* Y; a* R
bShowLantern=!bShowLantern;
) x7 ]: c; `; c2 l! _( P
+ f: \6 a& m! A$ `( c }: W3 ^3 [& P p( t* l3 [/ ^/ C
" D9 I* ?8 D% u X8 v7 n
if(bShowLantern)
0 @# d4 Q# n: Z6 G5 X! l4 c. `9 J& h; ?
{6 O! }" I {' x0 \2 j1 G! X
7 ?* n$ [; M; m1 o6 e //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
( e2 E4 Y5 D4 u
) P- P) O! G2 g5 s }
; {% ]6 A5 q& A" y( ~9 y5 q, j7 y. X/ S
else
1 {9 ]1 S% z" ~% a% C
) u3 q" G# S% e& t7 f' o- P' U$ c {
% h8 a: E" V4 E2 S, k% i# v. m& m, p$ K. T) L, M) `
//按透明度递增的方式显示“背景”图片0 `. F; u6 O. l9 B: k6 ?
5 E9 S- B0 R u( O; X AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
6 V8 F1 ~ z" s3 {. k' P# d. |( C* D+ C4 \. I- V3 {1 L8 a* D! K
}
# a3 x- e8 r/ a8 u
* @9 N& Y. t3 m- H. \, w% | CDialog::OnTimer(nIDEvent);* x9 h2 t( {+ G4 ]$ J A( `
7 B9 v Z+ V: c, | }1 q$ u* ]" x, _0 V4 i4 s4 ]8 E2 r$ d
* }' ?. r Z7 F9 H+ m
5 W7 D7 j! v4 q5 O 编译说明7 z/ H8 z/ s! T* v! C
3 d9 e0 ?! ?+ w) @* H# Q/ w$ ?+ G8 b; G/ }
由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|