|
|
周鸣扬
* R' E( [0 g6 D" M1 J; P: M# _. ^) h- r# o9 W
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
& X" @ i: b' y- Z, |/ y# T2 g- q1 v7 X3 d% [; `
0 d4 u& P8 o6 d- J7 X
解决方案/ \$ f. |2 f) {- ?" m
, l7 X' D! P( L& f% {2 b2 `/ W
! P9 ]; T$ l5 d; E4 M3 j 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:8 k) c& z% k7 D; V. [8 z
2 B/ b, i1 E$ B; a! t* d! R- p Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red " L8 v/ I9 u* ?+ e9 H! _5 q
* v5 y0 W0 x' ]$ Q0 j/ a1 q- g+ I Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green b5 Y( @1 P1 o9 R6 A
3 H" A% R6 R3 b! F, D2 O Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
6 ]: C5 ]% E! q+ [+ D4 F8 `- Z, ?
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' k7 X) K6 r" ~8 }/ K5 q7 n. t2 k; m0 n$ n/ Z4 p) L7 Z; |8 p8 ^
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:6 c4 C# ~# z5 P
$ L# M" d3 f/ m, Q* U L4 d- p- ]
BOOL AlphaBlend(
+ V$ u5 p! q! C8 M( f5 ~; d
" r/ e6 w) N3 y: ]6 T HDC hdcDest,
. e( \4 z0 Z8 T) ~& ^) K7 @; \1 V3 u
// 目标设备环境句柄
, a) B R: x, t8 Z/ }: T- V' I0 f( c) p$ F
int nXOriginDest,
4 P B Z: @$ @" p5 V* |2 U _9 d0 ]; _& R) p
// 目标坐标x
" k: I2 d3 }. V
1 W9 S) _# s4 V" G& _: ` int nYOriginDest, 8 F5 g/ E0 [ S8 s& h" F0 P
! l4 @9 `2 m; \
// 目标坐标y
. r* O" X$ ?7 |* H6 ~: ]
9 R9 S8 U$ o- u7 s) Q1 | int nWidthDest,
: R5 M! o. G5 @# Y2 e6 T5 G- h4 R) c w h6 u% j
// 目标宽度
) X# s+ \; F; H+ h+ G) N+ T9 N, K/ j5 I, o. d0 _: \" Z
int nHeightDest,
3 _7 c* l4 s; g$ C! N% z! D8 L( C5 f1 R4 A/ x9 L- v
//目标高度 c' b# Z9 a9 U' {, Z
: J. [% @& A$ }4 H HDC hdcSrc, 2 ^9 O) h0 z- i8 x
J: M. E5 v. Q \. j$ N
//源设备环境句柄8 h" ]/ e1 U& Z0 c) z0 B& B
3 L& T$ o# g0 A$ T( E) x; {: M int nXOriginSrc,
# b6 t1 r3 \6 [) m
R7 S4 {; [! q# x' V // 源坐标x
/ L+ r8 D; V: H' v7 r5 E
7 t$ W$ X9 Y1 ~, [0 m int nYOriginSrc, * w( J, C' [1 k
A* }% }- Z1 r; _9 Q: z // 源坐标y
4 Q1 m) t& W: J! C8 a! J3 }
7 R: y. A' }' V, g" {5 h5 c2 _ int nWidthSrc, ) z5 [* @: {: @+ ?) V
6 v8 ~! P1 B* J
//源宽度
9 @! P* b [2 h, x' E3 l3 w& V: z: w/ S) q1 K
int nHeightSrc, ( W- W' G2 Z- m w8 Q1 N" D. i
) z" E. [, I6 S( X( i
//源高度* z' k7 e/ ^! n/ o3 V
, W9 o7 m2 T9 A2 }# S BLENDFUNCTION blendFunction 1 B* O4 a( `, F2 G+ l0 g* s
* A5 F; E; V9 ?; u
// 合成方式具体数据结构' _$ h* u: F* }) {
2 k# o7 Z- J: Q3 x );
2 V/ o) r5 ?. F: d `, [) ~" g: R( O0 o( [# f% a
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
0 d& U8 N1 a5 r4 ~5 S8 L
; t$ [+ x1 \+ a6 u0 L. x- h. F typedef struct _BLENDFUNCTION {) A. N; g6 ?0 X8 D E0 |0 p
, _" B( v7 M5 p- d BYTE BlendOp;
9 d0 ], ]% m9 x) W( S6 l% n2 c( g8 k) Q
BYTE BlendFlags;//必须为零- y* `4 ^ `6 y
* w* S$ n; [3 I! h% e: g% g
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示. K" ^2 E2 q! m# h0 R6 j
" F& {7 E4 v# {" L, f; J4 z BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA+ k/ d! ~* z: z2 ], x
# k( j* P6 A' s8 \, t. E
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
, a( b1 a1 I8 J J# ^, E# c, l7 Q" x; G5 j4 N2 m6 J8 ^
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。3 g' c) Q; l# P, k6 j1 m8 |' V
- p$ ]# ~) P' x$ o9 R
j; t* U( ], C& R 编程实现/ s5 l& K* _( Q0 i" j
2 ~* }, {3 }0 p) U
3 B$ j' y6 V1 X1 P! q I1 _- v 了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
: O) M& }0 P" m$ z2 T1 C W) s6 C' N# }) x. W* J
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:* C! d: A9 I" l7 e; e) O4 v3 Q4 ]
+ T1 U- Y, Y5 } W7 x/ g9 w
class CWipeImageDlg : public CDialog+ E. N1 k/ R6 _' _, o+ Y
, `) x- o0 J$ p! A
{- e% `/ Q& L( l; Y0 F+ ]" O1 `
) t/ o9 l: \, L. f$ E; Y( [/ [ // Construction
: j9 }, s) t+ J* S$ h- a& c* D0 t0 E# |7 J% X1 c
public:
2 Y4 e W7 z4 t9 w
6 K( p p9 q9 J" I0 n5 F4 {* ? BLENDFUNCTION m_bf;
6 ~# {7 b. {! u4 A/ }/ d# w9 |. G+ L( v \2 S6 R( T
CBitmap cross,lantern;1 t5 ?# Z1 U7 A4 Y- `0 p" |
8 y. e. R' j; I/ Y' Q& U) ^ BITMAP bmp;
6 Q1 K: g6 R' n- X( l' h3 I+ d7 [
int bmpWidth,bmpHeight;
% ~) t3 g4 y. V' L! Q/ h2 y! i" D3 K9 i# [: _
CDC dcForCross,dcForLantern;
& j; ^% u: W h1 ]6 r! @4 F
" |# G( _- ?& n3 Q+ {( g5 | CDC? dc;
7 E0 n/ o& L1 G/ N; g: w" @) ^
7 K' X% h' L: G, I' u0 F BOOL bShowLantern;, w& u: I9 f- Y4 F9 i
& n- e( u2 P, Z e( W ………(系统自动生成部分)( {% @$ Q0 X6 e9 ~5 Y6 g) a. H
+ Y) P" q* O+ [0 `) D" }
};
* T$ o! n7 \/ _# b# w$ s1 g5 ]/ o* V* I4 R( k: I# w0 l
接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
5 Q' @4 E% Z$ ~( Z. u+ }9 ? C' V k+ z5 O
BOOL CWipeImageDlg::OnInitDialog()
8 r! u* c' K& a; _
- C# s q1 s5 K4 n2 ~" F {
5 l4 [1 I5 p% ]! H5 g9 J" \$ J" J( P4 u% }
………(系统自动生成部分)
. V. s; |0 J. V/ c- R U
% K5 K8 a1 G2 E @) {; t, Q // TODO: Add extra initialization here
9 R' W! O: }5 x' k& Y/ t
5 Q! @* n g }4 [! o" g+ s //初始化全局成员变量
% X* @9 c5 L7 y0 ]3 c
/ k3 s/ f; s/ G' O) W8 F+ F0 P" Q this->bShowLantern=TRUE;
& ?/ ]4 { Q! [9 \" p+ U$ i$ j( C1 k; e: ], a2 {
m_bf.BlendOp = AC_SRC_OVER;
( c, B5 h5 r. O; [" l7 M# Q$ P$ S$ s. d
m_bf.BlendFlags = 0;
1 ~+ r+ Q8 Y; [& c9 [" b/ X ~) `! L2 o9 [
m_bf.SourceConstantAlpha =10;- m; B. h' ]% Z: ?# f$ d" J! S1 Q
: ]/ K) m7 j$ U( { m_bf.AlphaFormat = 0;
, ?. h: m* B+ m Y) F. \
) s; [9 o$ @1 q1 s: ? //为节约篇幅,以下代码中略去对操作不成功的处理代码
3 T2 E) V3 \2 F7 l6 Q) x* d9 W) o/ Q H% Z% k. U+ x
if(!cross.LoadBitmap(IDB_CROSS))$ P: e. A' R! Z& U2 x9 G& P8 i5 H
; \7 ?* k: } u" I- \5 O {
4 g3 N* \2 M e0 N- V1 E0 X6 p% m: v4 Q0 w# {, Y
AfxMessageBox("装载位图出错!");
* l; L7 U8 N: c. }) I, c7 j; |; x/ j, O# h
return FALSE;/ g% K+ f- U) n9 l' ?' [0 w
% e% U, w9 C7 W, v5 T6 u
}6 ], F$ M3 K# \8 k
8 ]* R6 r9 z' m cross.GetBitmap(&&bmp);
; ^7 C; j# u5 x* E9 z( z0 h1 {
2 H* k; B0 x1 |. A% y lantern.LoadBitmap(IDB_LANTERN);
% {" V2 i* U' N/ u( T% d, I/ N/ k! x
/ ~" j3 J+ u* ^ z) S J. A' A cross.GetBitmap(&&bmp);
( z D2 N; J9 o5 y. R4 |: L6 o+ k8 m" @+ W4 Y6 R0 ^
//获得位图的大小信息
; P$ X( r3 ?2 H2 @) S
4 E/ x; \8 i; g0 o) t9 l- X6 V: w0 T bmpWidth=bmp.bmWidth;
7 ^( _- }9 w+ k- D3 o! R5 {% n m& Y2 y3 Y6 \
bmpHeight=bmp.bmHeight;/ P( a! ~# G H
8 u( u6 U) p! | D; G dc=this->GetDC();" v/ D( r7 C8 h4 |: h& }, ]) @
4 [1 w$ R; G3 b7 I" o" w& L- A
dcForCross.CreateCompatibleDC(dc); [5 E, b8 a* R6 R1 o
( }4 _: Q; m3 d% J6 L2 K
dcForLantern.CreateCompatibleDC(dc);
, G* I( b' F. \( P# Q5 P$ L, Y' |3 P8 U( d8 q& i# G) y1 ~6 \4 q
//将位图装入设备环境句柄
; X/ _( b2 e& i( ~/ v1 _2 v- E4 G: E ], P( q
dcForCross.SelectObject(&&cross);/ n- |1 J# U6 E0 q S: {
8 k& X& u6 |/ [- W
dcForLantern.SelectObject(&&lantern);
6 x& \' S9 b! ~2 N! a. O7 k) X- Z. d9 a; l
//打开计时器. M4 \, U# e$ Y+ F& a" s
9 Z8 H8 b s. v0 q: A5 a' b+ e, U
SetTimer(1000,50,NULL);
1 d6 a8 W+ i& ^4 l5 l) A. Q9 v# Q
return TRUE;
; Y0 K3 C2 K3 m+ O/ S
' f H+ {( l8 }4 E4 C/ E- C }& |5 o( p$ a' F0 L# ~3 J" N6 Q7 D
( f# E$ O( |% s+ x& F! i void CWipeImageDlg::OnTimer(UINT nIDEvent)) G1 ^ r8 \8 \: }, E
+ o2 s( a% E. t1 l L7 W' R
{4 N: w* b/ V) a& T/ h
: I0 |/ j: f/ M( i. S. h8 Z, H //图片透明度每次递增5点( y9 j$ e! I( t1 G
% G( u* ]+ c! i( J6 T8 H r m_bf.SourceConstantAlpha+=5;& N$ a: R. ]. ]+ S" r& F* Y
! Z( m3 ]- T+ O: s //当第一幅图片完全可见之后,显示另一张图片) V$ F# n- B" A6 i
) [ A% x7 ~6 r" y( |7 { if(m_bf.SourceConstantAlpha>=200)2 r/ a2 k e3 n) B" b/ Q: ?# d
; D. k4 o3 p" P, H3 p8 a. B
{
; n/ Z5 X( ~, d$ b0 z6 j9 G$ h, K0 y; @ {9 h- f; Y$ z
m_bf.SourceConstantAlpha=10;
0 D% S2 R: l, `/ Z$ I% V% ?6 r. i- x( s7 r% Y# k
//将bShowLantern做为显示标志,确认应该显示哪一张图片
. Q! [+ K( h. A G
; \) l d2 @$ T bShowLantern=!bShowLantern;- g' d: s& r |, w# Z7 y" ]
1 X! N; T t; d7 m0 P }
& d5 @* N7 [- I0 P: F7 ?. Q) X9 G+ n! w6 q- r" W
if(bShowLantern)
6 {) C+ k& k$ s9 s: I$ s# P' p
5 }) H& ? u- C/ v. |- Z, \6 p {! i0 _3 F. O" u) v# O- y/ \
6 U" [1 {- ~4 ^ //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
) d, i4 M: m7 L8 U/ v8 u% T
3 y P: `4 w, x8 D% F l }
8 I5 l9 N+ B0 |2 M4 p4 B5 t3 W8 u1 z: x2 D& e
else
" `5 W8 R/ M# \% `! ~/ \7 N$ Y8 ]9 ?! @
{$ k5 ^! N/ s% ]! z# v
1 Q$ H6 o$ Y/ t4 Y |! S u //按透明度递增的方式显示“背景”图片5 o$ I% S$ c& Y! b: \ g
& `% T6 f# O. j5 u) \
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
, g* @& ~5 u. r3 `; R# K6 s4 {3 P$ [* Y7 b3 A
}& C8 j" k+ U9 n2 t' p4 S& X! k
) g2 Q5 \4 B1 K2 c CDialog::OnTimer(nIDEvent);
/ ^2 { j. s* B& R$ \! Q9 S
/ e9 H% w Y+ u9 [" \& w( `5 t }
+ n# P$ G% @1 g
# |' x T8 V( x# F
4 `5 \& Z, N7 z/ s2 C 编译说明) y8 K& V- N" X/ }! m
, s* r8 ~% ]/ `7 H
6 V+ E9 `5 o. L m2 e, W 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|