|
|
周鸣扬 . u0 X; T# m5 E6 D. \9 M$ j
V/ g, X; k3 b# E2 L
图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?( X+ I% r W5 @+ u) k; `
% X8 g" c, k7 m; C& [( v
, ]/ U9 I1 {- E6 z+ b4 @, G
解决方案9 L& r& D& l/ {9 W$ h f2 S* H! z
6 z/ U0 x& r& \* @! \) H. S: l/ u5 _: v
不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:: Q+ Z$ r# ^5 ?
* j! V. I k* U9 t2 [ Dst.Red = Src.Red+ (1 - Src.Alpha) ? Dst.Red # K. L0 O4 t, N* @
* A9 x/ X1 N3 a8 ^( k- ?- u8 P/ P
Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
q3 N( X. G/ i( I. e& s% n2 g+ ?5 J0 J! s! B c3 j* }% q7 `. x3 k
Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
% u. s! }0 R$ e% Y6 T' X6 R
+ B/ I' o$ \' C0 `! t7 _ 从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。9 K/ q) l8 @1 @! \2 T
! d. ^" O% z# ? 在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:4 g, `! h4 }( W2 K# t( e' {
/ z+ M/ N A$ s: A BOOL AlphaBlend(2 @0 y. p3 _, o3 N. h+ ]3 b
5 R; e D$ w* E! w8 d
HDC hdcDest, % P; j4 i2 L# Q( S! \5 Q9 _
# J- Y, l0 J: B& }* D) V5 G
// 目标设备环境句柄
1 U: r0 A8 ~/ n3 m' k4 o1 d0 u5 E5 M: D5 b4 g
int nXOriginDest, % t0 z+ ]2 J/ f
5 x) [$ A* i- l7 |8 r // 目标坐标x( ~6 P, z" i, H/ u# n" i
. l3 W( Z9 `8 m! N3 f3 \ int nYOriginDest,
, h$ G* ?* s& G8 V! I/ q6 x
( c8 p7 f) B% w8 T // 目标坐标y
n4 W/ f4 t' M, T/ b# A% H8 R$ r: e& ?% {* `9 L
int nWidthDest, 3 Z! j! D8 x( |9 }
4 _ K! L U. U6 i! u7 K // 目标宽度& e9 G2 o% Z6 \$ A0 P
8 j1 R7 O, ^+ m, S p. D int nHeightDest, " K4 g1 R7 k9 c% P3 Y8 c2 T
) H+ i. L( C! g/ \* d5 ^3 k
//目标高度+ B# f2 [" v, a, v
) U3 P1 ^/ e* |/ I HDC hdcSrc,
% S! z+ E; E- g) T' R" U$ A7 f! D. L9 a L. T4 n9 A
//源设备环境句柄
% |3 N+ g/ \- e, [% V6 r7 C
9 `2 i0 |( w$ o r) L7 B+ W, P: L int nXOriginSrc,
4 \" b7 G0 Q2 M5 {- ?9 M( b- M4 a
// 源坐标x
V/ Y9 F( N! ~4 }# R8 ~
, @. S I0 |, A9 L T Q int nYOriginSrc, X' S t: D* I# i2 N( P0 T+ ]1 m4 ?
, K& K3 T1 b4 e* B3 b V
// 源坐标y
# v$ Q& d6 S1 w0 i
2 t: ]2 p. X0 a* p* ~4 F l int nWidthSrc,
" f5 J# a9 h& Z- D& J9 q9 ?1 m/ S0 D: M2 `% X" F
//源宽度
w. ^( b U; g3 {9 j! M5 g) g6 t6 G& a: w
int nHeightSrc,
# N9 J& I9 ^% |- w m F
* O+ a, I0 S, p, }9 o& o //源高度
6 v0 I; R w1 l! h6 C/ ? D; r+ w! o$ w7 H( x. }4 R
BLENDFUNCTION blendFunction * C9 R) W1 J9 E- D
2 T+ L; ?7 _) m3 z; @: J
// 合成方式具体数据结构
. p- {, U! @$ o6 A
& j6 e2 j5 w v7 @9 R );- x4 z: k6 w: S3 X
- x$ D5 M/ s& {! l5 y" w
BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
% z7 a8 P) F; s/ U' o: I; F' {7 H& y
; l2 {1 p$ X) j" M8 Q, X, a typedef struct _BLENDFUNCTION {
) J. ] S) V& o7 X: z: p: t1 X8 `1 A* J
BYTE BlendOp;
& y8 ?: C* w: v( D5 H& j# E( K# `; G) k
BYTE BlendFlags;//必须为零" I3 D# Y" [& T. l' a: \* l6 ~- A6 A
1 t+ I0 y( `7 G9 P" j o! J3 }; r
BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
7 p3 S5 y( }% x4 k4 t. W$ A7 v) t2 V! [. P
BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
9 j9 V; t8 t" e) \4 \* d% g `7 z2 P9 ?$ A G( y) H
}BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;7 o9 T9 E( c- T; f4 M+ W9 P0 ^: K
0 _0 \& a; q) X% O/ x: X5 \3 T
由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
1 n8 d7 R' c5 ^# P
, X! \9 o* Z2 h7 y. {
3 A/ A0 y6 ]; \ 编程实现
6 h" M4 v& H: I9 X9 b: \% G7 Z8 U$ S' [) K! S& S, R+ R
% z9 c0 } k2 K& M4 P N
了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。, | E) C. ?4 n, ]- s+ z
/ r( n0 s8 t' d
首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
+ }; N! y/ q* M; L" i
! w) E& c- [" e( l* O class CWipeImageDlg : public CDialog
# P- W) I+ t2 i1 l5 a q! X$ _6 T1 A1 E' W& f- q: x4 E
{
6 I$ o c8 c# G6 S' @3 J; @
$ F' C ~+ q( K3 k6 A* c // Construction1 c, @' G8 T! B6 W, K
# ]* Q7 F) _4 j$ F( H
public:+ o. |* F# j F9 x
3 V; `" e: F* Q( ]
BLENDFUNCTION m_bf;; y! Y6 \/ a# v1 s, f& O" c: h7 r
/ @3 W$ \ y4 `7 C
CBitmap cross,lantern;
1 C1 B6 u A8 T& m3 W) |7 i, ~# w l2 h9 S6 x3 a) |9 g
BITMAP bmp; T3 T& I; @0 z% C2 e
4 r; P0 A% o. g$ T8 u: Z- \# F int bmpWidth,bmpHeight;
7 ] o: j% Z+ h! t; g
0 Z9 p- `1 Y" k- Q CDC dcForCross,dcForLantern;
" D* I# d5 p% z: _
& r+ ~4 V1 Q* _$ K CDC? dc;
" `6 \9 [9 F* a- a, x+ w% K: Q2 R8 K, C) h# B9 j5 j
BOOL bShowLantern;
8 ~9 s9 D# G% m* d7 F+ G
Z- G) a9 r' }% o ………(系统自动生成部分)
, Y/ W6 d5 {8 y7 c0 |3 \# M. J: |! M/ \1 E I9 v
}; " m! P0 H+ C3 W# Y& U, u" d7 v
- H' k. Z6 ^' j) _ 接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:4 r( l) Z& K; ^% U4 x
% V" W' K# w- m$ u5 d BOOL CWipeImageDlg::OnInitDialog()* k/ z) U; r* _7 v( a' s- F% R
' y( |1 Y3 E: q/ y; i" E, ?
{
0 w- j- B) f6 L4 g# ]. Y' f* E2 q* a/ P' K# k" R
………(系统自动生成部分)
# p; ~- G+ g. A$ Q9 R/ w7 \. H& U! n
// TODO: Add extra initialization here. P" T* g' w5 |# f& O D7 G% Q$ s
) _- E. K1 W- O& M. e
//初始化全局成员变量
- K2 t: C3 L0 m8 o8 Z* P% f" X; E. Z% V1 Y+ @
this->bShowLantern=TRUE;
: z) e7 m- @9 @+ P" N$ |6 p$ i- f/ e) `8 s
m_bf.BlendOp = AC_SRC_OVER;
1 ?, E, Y7 R( V2 I* x
$ s" S4 T. F# K3 B m_bf.BlendFlags = 0;
$ w$ _0 I' J J2 _! m, ^* R, \/ N! L4 h. v1 X3 ~. X
m_bf.SourceConstantAlpha =10;5 A% W) o5 D5 E7 u/ Y2 X, {
% S6 e7 T; t" U J; k! J' O+ n b m_bf.AlphaFormat = 0;. s1 L' O# E5 w: x
, Z. c, G& H5 _4 p& \9 L
//为节约篇幅,以下代码中略去对操作不成功的处理代码7 a1 v2 i1 e( \. a4 V+ l
# H. V5 r. e8 y5 w: q& B; M
if(!cross.LoadBitmap(IDB_CROSS))1 M! Z4 O' n& w7 e) A8 E
9 k% z6 g4 n1 Y- e
{
1 w8 X1 j! p+ W5 r8 o' a4 K+ P8 |& @: |; [' }, C1 ^
AfxMessageBox("装载位图出错!");
- }( Z! _' {& i; e9 i+ t4 |4 u8 E3 l
7 K N* S; A& ?( v* j return FALSE;/ z* @0 B" y# ]4 i
1 l5 G6 F5 o+ `% f. w! M5 ^ }
5 k- i; F1 p/ ]* p8 J
' y" F3 b' I8 w6 y% {$ |1 E/ v) v cross.GetBitmap(&&bmp);+ a, b4 y6 o; f4 p N
; S. \' w8 U3 b- Q* F
lantern.LoadBitmap(IDB_LANTERN);1 v) w3 a5 s6 Z2 z. ]& d
: b3 Q& c/ w6 c+ V
cross.GetBitmap(&&bmp);
% h: ~( o7 K: [
, b7 K9 N4 J0 c% A; _: I1 | //获得位图的大小信息+ `9 G. H' d$ ?% U. ^! f0 d. \
" O+ Q8 U. \- V bmpWidth=bmp.bmWidth;( H3 j5 o$ N) d4 P* u. J
) }8 O/ l) \; S0 Z6 k) O/ r% i bmpHeight=bmp.bmHeight;
& O+ \% v8 \) m* }3 k/ M
% ^6 {$ \2 V# ]/ \. \; r6 H+ ~$ ]1 Z" F. L dc=this->GetDC();
, v0 b2 S, A2 q9 E* |* `2 i; M; o7 l& X
- K6 T3 a" t) ~" I dcForCross.CreateCompatibleDC(dc);8 H, h/ i$ v! ^" }% H. P, o. O
. |( P% Z$ G! j1 A- U
dcForLantern.CreateCompatibleDC(dc);* E* J( o/ U$ D) C1 {
9 N' G& C$ ^) k( I8 O! a( p6 ]. J
//将位图装入设备环境句柄
: A/ _! w$ C" F, f& | e2 d+ |! V, s) V1 o+ q4 K
dcForCross.SelectObject(&&cross);
! q7 O" v+ R' e- a9 V& A! N/ Q. _
1 r$ l# N/ `: Y dcForLantern.SelectObject(&&lantern);1 @, e+ T" T4 i& ~0 t) h: l
% @- c- g5 z5 S' D0 ]- g' X6 V; Q
//打开计时器- W [% T1 ]0 Y* t) {' T5 d% x
' N) y w- Y8 J, K; X
SetTimer(1000,50,NULL);* b/ R4 m( ]) |& w' ?, |) b! w! z
# M7 ^2 x* _- | return TRUE;
3 j i3 F& _# r" J1 Q, x
2 o% x3 k: k3 o7 p }
0 E$ Y# w% ?9 f5 t7 b9 l$ A2 A
5 b1 m7 z" Z/ T" L void CWipeImageDlg::OnTimer(UINT nIDEvent)
) N3 h" l: a Z/ A u% o$ |$ J" M" r4 Y v, O
{
* A- u6 G/ {% U7 \
$ P6 f) F4 r$ N( g //图片透明度每次递增5点( E. ^- K# x$ e( M* A5 j9 v" o
0 r) `0 K& e5 K0 J& Y7 ]4 B m_bf.SourceConstantAlpha+=5;2 N3 N# B4 |1 V4 }' g! N \% b
4 u! f/ c% x; x7 g/ m/ R: `
//当第一幅图片完全可见之后,显示另一张图片7 u8 l1 F) H3 Z& E+ y9 b. w
9 U4 N O6 h' w# D if(m_bf.SourceConstantAlpha>=200)9 _) N R. e: Z5 Z# W, o( c! r
: x. v5 L# q" L9 ^4 R+ }( [
{, o) [- o% @6 m* I' E5 v
# ~6 H4 }7 k I' Z9 x
m_bf.SourceConstantAlpha=10;- W$ g* S! C. e( f% Y3 l: O
5 C0 z9 p% h; O( A! ?2 Z/ K3 |
//将bShowLantern做为显示标志,确认应该显示哪一张图片 / @; u$ x9 j% F7 P
5 }$ q/ i o8 |9 m bShowLantern=!bShowLantern;
3 l6 n9 [: x9 I+ Y
7 h4 d- D$ t! ` }
3 L$ P' V% [( K
- Z0 b# X( y2 K- e# [ if(bShowLantern)) {+ \* J* l7 E$ n8 y; v
2 A8 g2 m( H) ?* r# y% ~
{
# R" Z' V# H& h- U5 P) R2 }8 t/ K b7 F) O( B' r
//按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
! r! W6 i$ s3 [4 C* S5 I
8 T0 t8 B# Z" `5 q: @ }1 y# N, B3 E& h9 B
; J' [6 M& b$ e( U! S else
% J% v3 H8 }2 |' n: R1 Z. L2 S* ]1 G: J T/ N( {& e
{( M( { T8 @% }! h3 Q$ y& o. L
6 h) b1 p; V0 B5 R* E9 E( p //按透明度递增的方式显示“背景”图片
: m- o( j* A N2 Y9 E4 Z
+ [ b4 A t; w& F AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
- ]$ a& `" A$ u2 N: E/ t4 X1 r1 |% ^$ O9 |! B
}- T; }0 W4 e2 d( U$ R/ {+ e0 w- [
' W/ k- ^3 r6 ]+ u1 g
CDialog::OnTimer(nIDEvent);' Q6 r4 i, f5 V9 u- \# p
. [* A6 o3 ?; u/ n7 T; W( K }5 k! \- F: ^% }2 `" |
( D' Q8 w( `) G% L! {$ d
; n x, s2 A2 u" q0 n5 j& c
编译说明
3 }" e$ W1 E; z! s5 z) g' B" Z8 E
! q. X: V* _. d: ^1 t' i 由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。 |
|