找回密码
 注册
搜索
查看: 4800|回复: 0

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
' \, A$ N1 i3 p9 B4 d9 [4 U4 ?  `8 m: P+ u! C! m
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?9 N* A6 @3 t+ j1 x4 K" @
! a: C6 c. M6 V4 I: X: y) {( @# r; ^

( h6 ~3 x8 `' S  M* m* i, y. n  解决方案, n* B  q$ m0 ^# |" [% n
+ o, N1 q$ O, b; ^% x4 g

/ m3 h" Q; J5 A3 m6 c# E  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:* A7 w; v) q5 |- G8 W

- m& A8 n0 T- U  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
& }4 m+ z" f6 A
' N% S- {3 y2 m1 ^! m$ U3 s  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ( B" j8 r( q3 x8 q$ ]" C0 ^& b/ g
4 R- n. p( }9 c
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
# U0 Y( u* o2 ]2 J4 \
1 A& p- A  ]8 g* Z5 F8 G8 k  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。4 \$ y* y: R4 G6 i# @( \* A

/ W" b; |9 T. v3 B5 u: N$ f! `  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
1 U3 X3 H9 a4 z3 i2 g7 v9 b& A8 w
  BOOL AlphaBlend(3 _* q: |9 ?! r* R) ~( q+ U
5 ~  U2 q6 }. R2 J4 c
   HDC hdcDest, 2 \; c4 T4 ~0 J. ]  J
" ]9 Q. P3 e  g5 Y8 `6 g  S
  // 目标设备环境句柄
/ G2 }( c8 E: B, B9 o4 m+ H
6 G  _& d4 G# D7 ^# l$ W9 f3 G   int nXOriginDest, $ U; I& a) Y$ M# {  H
2 F" r' ^4 u) Z3 ^
  // 目标坐标x
6 J$ v" n, M' U/ m
/ D9 @8 F- ]( R   int nYOriginDest,
2 J9 _6 r- u9 h% G" s3 D3 I
- F$ I( p, z7 n* q5 F8 o  // 目标坐标y" p- k3 S; [# `4 H; H1 L! ?; _

4 b, `3 [& Q! k   int nWidthDest,
" q. U0 l  X5 e! s+ ?$ j
" G5 _) J6 c9 Y8 v* ^/ G  // 目标宽度
, W% W( ?* _; b8 ~- W
7 I) Y0 u6 A7 b! I' l   int nHeightDest,
! I& Q9 ?" u3 ^( t7 R5 ~+ G: p
1 V2 [7 |6 X! x7 ?  //目标高度
, M5 o+ x& n- P( N
( S1 E% i1 T% t1 H   HDC hdcSrc,
5 ~$ K: V+ S/ A0 l! P* M* ]0 i  I$ C! g/ S, G3 C2 z
  //源设备环境句柄% ]5 ^  c) k6 _1 J# _9 w8 c8 p& J

% }8 }- r2 C' E& L0 n/ r   int nXOriginSrc, * a0 C! r6 \/ L
# M. w$ ~% G/ R3 r
  // 源坐标x
  X8 R; H- t. r1 o: y! p0 O
& q0 C+ R: A% m, T* f# j% ]   int nYOriginSrc, ; j4 {' h0 r( v1 [- {7 j

$ V3 m7 O# k1 u8 }" U, i  // 源坐标y: H- p9 R: q4 ^6 U% K% F
" l( `7 i) T6 m; v/ t8 e0 [' H6 N' r
   int nWidthSrc,
+ D1 A( B2 o5 \* ~. S& t1 M, Q% V& ~; _" @
  //源宽度7 b. D# d/ [4 @) w& x3 K) E

0 X% ~) _. }/ V6 x0 r   int nHeightSrc, , K! E2 V3 l( T1 r; ~4 ^! i

* N' @# H1 {  |- s% Q  //源高度
# G4 {/ J& L/ ^. x. A  C5 Q" D3 {0 x1 o3 L* s; ^
   BLENDFUNCTION blendFunction
( L, i4 Y0 T+ P2 U) _$ F( D7 d/ Z0 d8 d% N3 @- {
  // 合成方式具体数据结构; Q& {: v# K1 u
% b0 T( d2 Z1 p+ ~
  );
# M# S0 A6 m9 E# O& l( U4 W3 t# L. L: H/ h6 V7 v+ K( h
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
  T/ k. ~7 K5 e$ a
6 o9 j1 f& J& d& ?$ K  typedef struct _BLENDFUNCTION {- L7 [4 \7 l8 O1 E
: I; E# z0 Q8 C* T& z# A4 h
   BYTE BlendOp;: B- z0 X9 C* E2 V: c8 @) J
7 r: l) s3 q# t/ p; m. }
   BYTE BlendFlags;//必须为零
1 }, s# }) n2 H+ X, O' b5 }3 Z9 l+ U& H; x5 n% J7 W+ [! d
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
/ h" y0 N4 q7 |6 b7 @& s. E% A( y+ h# ?  n& U" j
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
. ?$ I& u. S6 E, e9 `6 r) _% k% r' ~: O/ I4 A6 \9 B* p
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
5 k. Q" F. i8 p+ }
) v% q7 \8 F' h2 w  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
  s* B3 {$ |  q: r) O; K8 ~' s+ r2 F+ C9 ?' }, R* l
4 d; k  n. j5 H1 L- t1 u
  编程实现
/ n" E# V; N: j
/ S* |& h  M- V) J" q
+ ^6 @( C, p+ h* X  z- G  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。2 y4 G- i' R0 c- l& I
+ w" b1 ?1 |4 L5 G7 b8 z
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
9 Z5 x% y. ?- v8 r$ t1 d/ ?
5 D% y6 c7 e- B* r- o  I  class CWipeImageDlg : public CDialog
3 W3 O) [. u3 e" V6 \( {, A4 [" x1 ?1 s! T2 `
  {
; Q2 U; s: F  ?& m( M
! ?" q" G/ F: ?! @# `9 J  // Construction' _0 C, Z+ c$ U" t/ t

. T& @: l, y) x! F4 ~" ^3 U! z  public:
7 J3 }! Z+ \; V  T! d3 H8 t; O& [( C. v, q* F# C; q" ~! B/ L
   BLENDFUNCTION m_bf;
: [# |; b1 o& D! d5 h% y
+ Q$ p# j6 M5 a$ w# I4 u   CBitmap cross,lantern;4 O6 D9 F% C  S1 r) ?
: Q4 O) g* m! u: E4 P, n  g
   BITMAP bmp;2 r$ m7 m2 b7 G. q5 m, b
# y7 d' n( A( u! L; ^
   int bmpWidth,bmpHeight;( B% r# |: `) J/ ?$ e

, [+ d4 [- ~8 n& Y) P   CDC dcForCross,dcForLantern;# `+ f; H% f& p8 I4 O4 Y

, T( p$ j/ D8 w0 R* d% o2 I   CDC? dc;( M/ t' u4 }6 \) o. u

' v" k0 B2 o2 z+ R# ]+ K( N1 P   BOOL bShowLantern;9 ?" {( ?6 V- f4 ?# @" Q

, k/ H7 E, p* f, G  ………(系统自动生成部分)
; y+ N- k$ ]" O, I% O8 D- C# S8 }8 n2 I
  }; 
+ c: f! u: x: Z& g! G* g6 o- o: N0 E' v; b* s
  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:+ }8 p2 m* v# m! S$ S
; m4 V) H5 f: ?- R
  BOOL CWipeImageDlg::OnInitDialog()
6 Z  I" L) I4 K" P% t  p
, j# Z& A2 r( s+ N% D  {
* h! c' o& {$ a: ~! N  D1 X# u4 O! M$ `( y
  ………(系统自动生成部分)
- G+ T! l# G2 Z8 G/ s: z5 f7 b% R6 z
   // TODO: Add extra initialization here7 K+ K; ^% y! x* t5 `+ e$ [. i

! F9 g1 F2 _* L3 Z   //初始化全局成员变量
  o* f) t0 Q, V# S
# ]( \, C, K8 F' n8 y* M   this->bShowLantern=TRUE;
7 e# _: v( ]$ l( h6 B. D. p4 V
1 O4 g% |# r" s% q( a   m_bf.BlendOp = AC_SRC_OVER;
# S/ R( V1 b: c: i! u5 q( t$ x5 E9 J7 j% B# M2 O+ x/ U
   m_bf.BlendFlags = 0;" s  J/ S4 P$ H* \

# {; ?9 }( \; n# g. J; Q   m_bf.SourceConstantAlpha =10;
$ Y# @2 m; o" z4 b0 y7 O# I1 i+ J
   m_bf.AlphaFormat = 0;8 N1 M! P5 Z( }) w0 f
( A# A; \' r+ @, C  U
   //为节约篇幅,以下代码中略去对操作不成功的处理代码* h( U" N* X; C; z9 q/ d
0 Z& F" _5 G% i$ ~# p
   if(!cross.LoadBitmap(IDB_CROSS))0 w0 u6 q0 b1 y2 ]2 O3 s
9 A. _, t8 d& q- l2 k
   {
. ?- ?% o. P$ e! p  g
/ o' ?+ n$ _; c( m9 |6 c* B5 Y   AfxMessageBox("装载位图出错!");
  i( C' v) V0 f' F6 p1 t8 Y5 j, n: |  m/ ]
   return FALSE;8 v5 ]0 @( f( d* ?5 V9 j

' v  g; a: D- c5 S8 K  w. f3 H   }
' Z) K) \' L7 A! d/ f( W+ u# u- M. H0 A/ s) [
   cross.GetBitmap(&&bmp);
4 @: r( B' v2 M' \. w" [1 }* b$ ]" e% K
   lantern.LoadBitmap(IDB_LANTERN);* F' K- L6 }% n, F: B
) v% P, @# m- b: j( ]  \4 `6 _, w! L
   cross.GetBitmap(&&bmp);% F* y% x6 q, Z) w6 x

! L( x6 i% e% s   //获得位图的大小信息* F. p: T) D5 `" h# N
3 I( R6 @2 U; s8 x
   bmpWidth=bmp.bmWidth;
2 C2 P" r3 y+ h  E! n1 x8 r8 s
9 B# a2 P& M. l, l" p9 N3 a' [   bmpHeight=bmp.bmHeight;
9 z' R1 }- M" @3 s4 v: d  d/ D. b- Q/ M# t
  dc=this->GetDC();+ J' ?0 C* q. a; _

3 F" q6 S% t% |( C+ W  n. V   dcForCross.CreateCompatibleDC(dc);
6 X* f8 T- V9 J) T
% h$ }4 [+ h. t   dcForLantern.CreateCompatibleDC(dc);0 `2 J4 Q9 O$ q! b+ B) Y
3 [. k7 r. n9 O8 J
   //将位图装入设备环境句柄+ R3 e! f$ T3 [9 t% Y! n

- @! {  z7 N. w   dcForCross.SelectObject(&&cross);
4 P1 X2 j8 |) ^; M; J. p' a5 z
: S/ f: D$ E, O9 w   dcForLantern.SelectObject(&&lantern);
0 \1 {/ _7 i! p/ x
8 v/ i! T8 j" j1 u" X   //打开计时器
0 u8 H7 N3 C) }, T1 @& ~
4 I4 u6 j# n9 l; U  _1 W! x   SetTimer(1000,50,NULL);2 b2 I& P2 @: ~5 Z" c2 j) h

# ?  k( m: V( o) l   return TRUE;
  O* S% o+ h* R: ~9 ]0 w* G& V# I8 z# x) z9 f  T8 X! S5 r
  }
* g) w3 X; |9 b8 [5 y
4 n' W3 {' {( [9 X1 E2 w  void CWipeImageDlg::OnTimer(UINT nIDEvent)
. N7 }+ l" `, g- g
8 I4 E& K+ j* p; R% @5 V! h  d* ~  {
! [* G- `4 f) j4 ]# a6 P# e! g3 o0 ^* L% Y; {, S% Q7 m
   //图片透明度每次递增5点
9 R, B. z$ }3 n$ C
* ?' ?1 @; ?' Y% t5 r+ m   m_bf.SourceConstantAlpha+=5;
7 s9 O! o1 V; A/ N# t  D9 a, p! a( U3 ^" A3 O9 M  t1 N
   //当第一幅图片完全可见之后,显示另一张图片
5 u% B* R: y0 @4 S- r" ]4 d
7 `( h9 q8 y( [3 p$ G( o+ i$ n" R   if(m_bf.SourceConstantAlpha>=200)3 L1 c& A& |/ g+ b+ s

, ]- k4 K& C; K   {# z% U# D3 O# E7 z
: i# O& ^' _6 e( U8 ^2 p3 v+ g
   m_bf.SourceConstantAlpha=10;
6 X! u. Y! S/ C0 M% B; Y* c/ G& Y  z4 [% K
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
0 f5 e, N6 ~. I3 x( h  C+ d7 X/ _  h; [: C
   bShowLantern=!bShowLantern;
% O7 b$ K3 Z3 F- \: u3 d( t/ j. p8 }, ^2 F
   }7 D2 f/ \3 F9 A$ y
9 a1 A9 Y$ D! {. q% b
   if(bShowLantern)
' W: {# I% o4 L5 T# q! z6 m# {8 }! Q$ e# w) P1 b
   {
9 `, X3 ]" d( W; _2 r  I( }! G5 o1 S
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);* Q* U$ P# S+ T$ g$ H9 s) z6 `
5 p& R1 E* |& ]6 v- o
   }
# `' e9 D% J! X5 s# G: H0 Z
2 X* k1 N: s3 I4 b: p8 W   else
/ m( J$ K: G8 R3 z
+ N8 k8 \& v3 }5 Y) `   {+ |7 p4 g; _( K% i: N* L

) F& z$ H3 w( `! ^3 a; {   //按透明度递增的方式显示“背景”图片
7 T' C3 z/ L3 i- M7 t! p+ L! B5 h! b8 s, {/ H5 Z
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
! m- e8 v. B5 c, `9 P2 B; C+ M$ U( N: Z1 D
   }
3 _. W% r: ]! T& t, ]
  c- n& N6 B3 _; ?   CDialog::OnTimer(nIDEvent);& C  }& k- R0 d0 _( H
0 Y4 F3 e/ H1 N6 W+ F
  }
% N7 B  F3 u( T. j0 Z" y+ H6 v4 D5 w; k8 _6 ^: E" {) s
7 O4 v8 V) W* _! \
  编译说明+ b, b8 t' [0 F+ P8 O& U
* m  p* X9 e) g. q6 _6 r
0 v, I- D5 V8 h2 B" ~
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2026-8-13 22:37 , Processed in 0.016758 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表