|
|
周鸣扬 - Y g; e) D$ t; R! {
9 p4 \+ K" H/ M) {& ~" K
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?0 @# S8 f5 D9 d+ z+ H
+ Y; N0 f; X7 t. s4 V9 \7 M/ K
0 ?8 ^( V; o( L* z# C
解决方案
9 J) a2 c0 n. [7 h1 k @6 R0 K% I7 W. }4 v3 e* y! _5 y9 u, y
! D# P+ `) z9 u" L' c, r5 G! I 不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:( ?8 i8 J% }( d
5 |# { M, b( ~6 J5 Q Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red
5 a3 w7 _& E5 Z7 Y, K, h; s' T
$ j! [9 v9 y1 v: H- [$ Y Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ( ^$ ^6 M/ z: z" V& e7 }& q3 o# k
. H$ k. ]! O h: Z. ]
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
2 n, Y/ |5 {; s: e% ~. D1 s+ k* z6 c7 K& w0 x
从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
/ g! X" G) ^3 Q' M6 |8 ?0 p5 u, b2 d9 w) ~( J6 J1 y
在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
" S8 x4 t" x% X$ `! P% s) V
2 ?% r+ c/ _8 L* Y6 o BOOL AlphaBlend(
5 e" V" K3 g. k- |( `
/ B4 L; ^5 t" `# p HDC hdcDest, , n+ k$ L2 M7 f0 A
+ z' R1 Y* \9 x# a! l8 n2 U4 J
// 目标设备环境句柄7 `: ~- _9 m+ z* ?/ ^2 x
3 O! b+ k6 J9 O/ `, W: P8 n int nXOriginDest,
) {1 ~2 |: r; X" k! K, [
9 c; D! x5 b+ I, x! N: k, f // 目标坐标x
! X2 M6 Q9 C7 a# m, H _
$ ~; s+ F j% S9 {* P& O( I int nYOriginDest,
, }) u+ w A; l; ^' }8 \
. F. V$ n0 F% q // 目标坐标y
& i! |# G$ W. g1 `- f* \3 N2 H- a" [# z6 D: }8 K
int nWidthDest, : r6 t* Z, n B8 H
6 `+ W J" X5 i. [0 r, ^/ R // 目标宽度
& J3 X9 h4 v6 U4 K. @1 i4 q0 _
) d) y G0 ~1 e3 ?+ @* y8 r6 T int nHeightDest, . a. p2 M6 ]8 `
$ b9 G* ?9 l! J: A& ] //目标高度
: g. `/ |# r# S, `: b7 e" E9 h: K( V0 w( ~
HDC hdcSrc,
& a9 J- C( ]9 B B0 B. x: U X% v, b. |
//源设备环境句柄
) m' c5 i" _ u6 g$ n/ Z/ ~9 T q8 N; K1 g- S- t. l! o" {/ s
int nXOriginSrc,
% y2 S$ \% W" T
6 ]: ^/ u+ M) R // 源坐标x
1 f4 U( b" J( k! _: W- _! y) H& }! E( y2 D7 R8 W
int nYOriginSrc, 8 k! L) y2 ^+ M
( A% V4 K9 R6 |- @- _0 E |: v // 源坐标y! W5 q2 K8 ]2 Y( n) w
. ?. c' w* O. K$ g: |; i! Y int nWidthSrc,
$ O6 Q0 f" H+ a, f7 [; v
% T5 s1 H* N6 E4 d& y; o" l7 ~ //源宽度/ [; C/ {8 q- }7 v, ~% A8 K7 B
/ f$ ^0 U3 h+ ] int nHeightSrc,
; Y5 ~4 B# M5 F) _
& r8 l+ s6 N# S' Q* Z" |- S //源高度. _; r, p7 ~6 E' u
8 M! D# N" h2 f BLENDFUNCTION blendFunction . d6 Q5 x) o$ t) c" v3 ?
# d+ t9 t& A! A
// 合成方式具体数据结构; ~2 P6 m& \; D3 S" _- w
- ^# H2 @7 K* ~; F. z
);
! z% S/ W- A% {% V" E5 V3 q" p6 s5 s. l; u5 h
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:' z; Y9 j: G2 y6 }" o: x
) X$ O1 j T! L. n
typedef struct _BLENDFUNCTION {
* {& q' s' z9 @" `* l- f! X+ F& u
BYTE BlendOp;
0 F3 Z' n p5 e9 c9 ^
2 [2 [" i' s; z# P BYTE BlendFlags;//必须为零0 b. O4 W5 @( n5 q) A: T) u
- `, M W4 G; C0 ]5 r+ q; ] BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示8 [5 z) E/ H) l5 w, \4 K& t
" ~& r+ n. t& {* \' L BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA' b6 I3 _, U# o) D) K
, ?1 z# c; A6 M) S }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;& K1 ?, {. v/ e f2 e
7 o# f' ]4 k1 C: R$ f 由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。3 H) Z, y- B9 R$ a z3 @
+ F. q1 @, }! V8 W) I% C
' o; ?# R) y1 H- } 编程实现% \9 c8 B% K! r) `+ a
) P& {2 `6 t* u
) o) N( X3 i" \- L8 j$ T4 {
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。 r2 s) E; A' t# V& O3 r2 Q! x0 C
+ c+ {& d9 i8 G+ R& S5 s+ {
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
* {4 m7 N5 C) f5 a7 Y# @4 W" }; G# f: ?4 Z- O6 @, w
class CWipeImageDlg : public CDialog
- \' m1 J9 a) c- M4 {. W) E2 Q( o6 S; O, _$ l1 U9 R- x
{
. ?! a! `, s' G: J1 ?0 Z3 {6 }% b. v. }8 b2 P3 O9 n3 Z
// Construction
3 }- ^# d7 [/ t" Q; N4 e! `; T/ k$ s; i. [
public:
$ B3 k B% Q+ w5 J( N9 }7 O" _6 t/ e0 G. t9 O7 Y
BLENDFUNCTION m_bf;; X# k( d3 p: I# n! e
; B6 E: E$ q" K CBitmap cross,lantern;
9 i. a0 E; D9 ~$ k/ n1 _9 M5 b7 V% ^+ ~; d* o& f: s
BITMAP bmp;
0 P0 s6 q9 F) B! C( m/ G; `, T \: P+ Y) \% _4 K
int bmpWidth,bmpHeight;
+ L. q+ ~& v( p" F4 a4 Q# Q/ @, ~4 t& }
CDC dcForCross,dcForLantern;
( k2 b; l4 F; w" }2 o. x8 B1 i
/ X$ M/ o( H% z8 L# \ CDC? dc;
S+ e4 Q& H i. R5 X9 A% [( c
BOOL bShowLantern;
Y# c/ B7 L6 o% U& o1 T8 e
1 l2 E$ P3 O4 J, D1 H, @ ………(系统自动生成部分)/ Z( L) ~! E# Y! ]5 D; a5 p
' _& r( e& B! X# h };
5 r9 Y/ I! s0 t& \8 d" {
( J% r E2 @' N* @9 h3 z 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
# @5 c5 g5 N9 `6 g) l
3 N0 k5 n8 r8 ?" Z BOOL CWipeImageDlg::OnInitDialog()5 ~( e: \) { h8 M
6 ]( ^. a1 e9 w' _( J/ j0 c8 b {
! [7 R3 V& G0 P# S* f' Z% B' d4 y1 H/ c9 b' g: |
………(系统自动生成部分), N3 b& I2 e. U; q: l
: i6 s4 b- c1 y // TODO: Add extra initialization here, S$ w5 H, h2 n* ~+ G, d: v% L/ o
+ M) s. C2 k g% f7 o
//初始化全局成员变量
. I( ]) j v4 b" x& E9 f. W2 f
! T7 F% w4 |/ K' P# G+ a this->bShowLantern=TRUE;
! r0 K# |, s" [! I0 G7 @4 D" T6 b. l( g/ H5 @2 G6 n' k
m_bf.BlendOp = AC_SRC_OVER;
% r, x% A5 Y+ f Y' m2 A( }$ R8 [+ a( ^
m_bf.BlendFlags = 0;9 t& q" m4 `+ D' C: |
# `# i4 R: i/ @6 Z5 J4 T8 Y: h m_bf.SourceConstantAlpha =10;
4 d4 {1 E; w& ] K5 O0 Y. [, l* I0 c: `; F
m_bf.AlphaFormat = 0;$ z# N+ @# A7 E2 c7 i1 j f
6 L: M' q& r/ c! O: i; K
//为节约篇幅,以下代码中略去对操作不成功的处理代码6 Y5 l$ _0 L7 V8 C) G7 w
; W: R" @# u$ }8 e6 h
if(!cross.LoadBitmap(IDB_CROSS))3 `1 r3 ?% D/ ^" r: J
& B$ R) M( s$ ^/ ^; U8 S. m8 w
{
& N3 h) p8 Z; N' v) t
; [4 t9 [8 e3 X$ b8 K) r' ^ AfxMessageBox("装载位图出错!");
1 X: s- d+ ?7 U) M( ^6 [: }$ T8 ?8 {7 V9 `' R: N3 f2 N
return FALSE;
9 ]8 \; {( m; e+ w8 F e& A' v3 G' W+ K' f/ ]
}
# W) i$ S0 v, Y- \* Y$ G9 Q& J. x5 B% k) p `0 y
cross.GetBitmap(&&bmp);
. i, y/ s. v3 D$ c3 |
# H2 w4 D; O% f' x* l1 q4 a lantern.LoadBitmap(IDB_LANTERN);
+ f0 W$ i2 i0 B' a1 J; F% ~' r; P9 \
( I8 X3 \+ V$ [6 w1 O cross.GetBitmap(&&bmp);
* d7 t" L% z: ~" A" U+ j2 b/ z- l
8 @" R, f8 F) G //获得位图的大小信息, S6 @4 ~, Y" U- Y d
+ @# t1 ^' t% ? bmpWidth=bmp.bmWidth;( @. k( u9 |# M. ]- v, B( U
* ^. H) @0 H- U9 B" ^0 h9 h) z
bmpHeight=bmp.bmHeight;3 p; s: }' T/ Q) `" T
8 X# Q5 K5 s" g* I4 s; G dc=this->GetDC();
% s' t3 l% Q0 S: T
5 e/ \% w$ [4 G dcForCross.CreateCompatibleDC(dc);
0 Y4 M( {% @7 `6 X, t9 h1 b& u$ b6 r7 w1 | ~2 A, S* v3 Z
dcForLantern.CreateCompatibleDC(dc);. g4 k/ A2 x- V
& R% U( h Z5 s7 B4 W
//将位图装入设备环境句柄6 o1 `2 }) w1 ^2 e7 S2 C; o) Y
. @% k: w3 N h; {
dcForCross.SelectObject(&&cross);6 X; _: I3 O% E+ W8 @5 c
i& n% q4 H& j& [$ I" s" T dcForLantern.SelectObject(&&lantern);' m7 b6 m/ B3 c2 I# p5 }
' x9 i y6 [& I; _6 Q' l //打开计时器
" g3 C9 L5 d" ^& p1 R. ^8 S! e. S
8 s8 `: C% ]' u- \4 K% e" n SetTimer(1000,50,NULL);9 ~; f0 ?) \# X$ K
# g: Y; F* D, S; k/ v/ V0 C
return TRUE;
) `( ?3 { ^0 x D5 f" |& P' n) J) R5 E$ T7 E. w& T. \
}
* `$ i) T; r7 C9 v; Q: A0 r( ?+ q; Z
void CWipeImageDlg::OnTimer(UINT nIDEvent): d: l) Q) ^/ p% P4 V
]( j, g0 j0 Q
{
* i: y- c' r2 T% n1 B% g
; s! @1 x. `5 p2 _: r0 U //图片透明度每次递增5点& s( o7 L8 f# P: }6 n& \- H- K1 I- Q
( ?7 P& n0 f2 c
m_bf.SourceConstantAlpha+=5;
9 c9 {2 r% T q/ \! T% ]. S- A3 o5 o8 W" i! q5 A2 ]5 m
//当第一幅图片完全可见之后,显示另一张图片. e, y8 t- @/ q: y+ T3 f5 y0 }
( c2 H! }6 U7 c1 y, E if(m_bf.SourceConstantAlpha>=200)! \6 j l9 G; J; h
& O$ Q- t/ e- D$ e$ `& A) t" F {
5 F: C' d5 F( q/ d4 G; O9 p5 [1 w. j; e. [& Q- {
m_bf.SourceConstantAlpha=10;
* E4 Z" l6 u+ m2 Q. N4 d! D6 v8 i( K
//将bShowLantern做为显示标志,确认应该显示哪一张图片
$ X- o) F* V0 C0 q. _8 O
8 J3 w2 [9 Y; [" |, x% f bShowLantern=!bShowLantern;2 O& \% ]2 p: c+ s
8 _; f: q) W! } }
* f$ z& d4 J( z0 Z' ] h: m
6 v, |/ c) Y/ ]( Y' z if(bShowLantern)
' X8 ]- h& q! @: @# d9 e" M( t5 e- D
{
6 `, @7 W" B9 ^9 @+ F. o2 {+ Z4 |) P- M
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
0 X$ w/ s8 v O
: u' c0 T+ i, a7 U9 Z. t2 h, F }4 @* Z. t: r" u% J& V2 v- V3 W! P7 Y
, Y4 W% u1 l8 k/ P) E) F
else& z* Q' t/ ?4 }' G1 ?
( S$ M! @% i8 Z6 O& ?; n' _
{2 P# m2 e: I: }& `1 X
& @& o5 a6 R7 u" k //按透明度递增的方式显示“背景”图片
8 f$ N A# P1 K) }& b2 n, |' r3 t# n2 _' j' g# j! H. T
AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);( q2 r8 e; c: t( \ i# T* C
$ n. z$ N8 H9 B- Y
}
6 j6 L6 Y* j u% V. v8 D/ C( B# B$ t9 `. \# G( q$ J v( t) O
CDialog::OnTimer(nIDEvent);5 G# G! h5 h2 Q* j
; Y, m# A* N& r, [3 b; q( ]
}
6 z ~0 U+ P+ L) g' p+ Y. U' z% d
6 l0 Z8 G- w; I/ |9 O r# x G( q; |1 ~
编译说明
6 @% m# o4 U! R% W& |+ N
$ ?& W0 q8 v) W$ P
8 u/ L2 ^4 ~/ w$ x0 e+ J 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|