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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
5 Z9 V% z# \8 F1 Y) O2 _% s; R: L- b2 h
. U  Y. ]% P* x  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
( y4 d. y9 c( c6 {( n
( S& `0 s' R* `% e5 Z3 O) O' L2 J' c2 |, T' l
  解决方案
% B  W% k0 G4 S8 G- i( X9 \5 E% H( o4 O! V$ _: `8 l2 @

2 F+ K  z, [, P) d( k9 p5 C, @  p' o  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
/ y* f+ [9 q5 K* X% o4 p
. f" O5 `0 h9 I9 q. U  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red 5 F9 X! Q9 F8 ~; w+ P( V3 j6 l" _" |

6 p5 u$ {) |1 A' ^+ P0 H" ]7 b  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
  S; N! {3 E( q% c" B6 n/ e! L' A+ K! p# r
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue) V2 A" J7 D+ B# j8 {. f

) }2 x/ b0 \8 u+ m# z' J: o  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
9 g0 Z( _: r( R8 `; u
4 s9 T* B8 O1 i, q4 l& E  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:0 }4 q+ {+ I+ U8 X2 b- C& N

: e8 E1 Q4 Q# X* O8 ?  BOOL AlphaBlend(
) K! v$ d7 J2 T5 k; q  e0 Q* D% X! h
   HDC hdcDest,
; t; B( B3 S6 j  E5 g  y1 d/ E
0 C2 r  Q' l" U9 N5 A+ h+ {  // 目标设备环境句柄
: u1 k' x: d& T( f9 a1 ^* ?# p
) ?& j$ y" t" X9 l: k3 }0 \1 X; ^   int nXOriginDest,
- G# S6 Q9 [1 S* i5 @" G: s  m4 {
  // 目标坐标x# M: ^6 a8 D3 n

. U. C+ M* ]: W* {9 ]7 @- A   int nYOriginDest, # K8 V; C4 V; H) {
5 p3 S4 x2 c2 r& m
  // 目标坐标y' ^. }6 Q+ \$ Z+ A
6 Y( y: e% n. M3 r) O
   int nWidthDest,
  K" q3 F  h2 {2 E, n9 s7 d. d
  // 目标宽度0 C( e1 V: x- @9 P3 B- r
# x1 @# }% p. o9 \
   int nHeightDest,
0 u# ^" [% l6 K. H8 ]- Q9 w+ Y$ v- u1 [0 v
  //目标高度  C0 `- o+ A* g8 l0 W2 }

( V& g5 x, w/ D( N   HDC hdcSrc,
4 Q9 j9 |$ M, Q- r: @/ @7 z% v' d9 r7 b5 r3 T7 B4 W
  //源设备环境句柄
$ b  |  ?* N; B' x6 T7 h; d# f$ j
& G, Y. W- b$ {# M   int nXOriginSrc,
/ y/ r8 u; O" M5 B7 G8 l
' C% B5 i8 q6 u( ~+ I6 a3 Y9 @  // 源坐标x
4 H( V$ k: N8 p) }: Z! r) `% `+ P" I# b9 `% y
   int nYOriginSrc, & b' t- {# ?2 Y- {6 O( N

. |2 U  n2 [0 X# _0 e! z# m  // 源坐标y
% l! W7 F$ G8 d7 w+ V( S* {! {1 z* I7 K7 R6 O9 N
   int nWidthSrc,
% I7 ?7 R0 D* s1 i6 i  t5 m  `$ O% \8 _
  //源宽度" [" ]$ O* f; q- `! U6 v! W
; I4 |5 h9 M7 B3 ]
   int nHeightSrc, 1 `" H! X  o7 }+ ?1 \8 L, [
6 R; A2 u2 m, W0 [, y6 f1 v5 K8 F' t
  //源高度
$ d' B; S+ ~3 x; G
, b6 g0 G! p3 D: y* V7 D1 h. O- {   BLENDFUNCTION blendFunction ( U) j% b# {& K2 d4 r* _  V
. p4 L0 G) Z; M7 K
  // 合成方式具体数据结构
0 }7 Y4 z5 k* {+ o# N* [4 N4 e" d: W& r2 C
  );1 |( T% ~, x5 }

' q, w1 g' v5 h  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:/ V' w$ U+ T- u$ d4 m! t; n

3 ?9 g9 r% K6 ^% J' x0 g( S' r! i  typedef struct _BLENDFUNCTION {
1 K1 y8 O, x% J/ n! |+ P5 t; |$ h/ ?9 w
   BYTE BlendOp;$ i' X9 w& |9 Y

5 K6 m1 P: f) z5 \- E8 P   BYTE BlendFlags;//必须为零
4 V3 v& A, V, K8 c9 b* \* w3 f
/ U8 A2 F" t" a2 U: M* {0 \& }   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示! I" Q" y- y4 `
. j7 l. T; C% T  s
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
5 T/ i0 q, w" E/ w7 Q$ ]$ z/ [  Y3 }- K
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
# X, }% ]* w6 r8 t; A- B1 @0 b  l  J9 J# \
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。3 x$ q9 B: S& [- E* M- U
& O% {- \1 j, z" G( s' X
/ Q2 a3 G& }0 q* X, D
  编程实现$ t2 a1 n7 Y$ W7 t$ W$ O% ^& q
$ F4 r! o5 l1 H/ X& ]& }6 ]

3 u5 j9 W) T7 K+ p- t5 l  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。  d6 e" A* v9 S" {

4 s( F; Y% @7 i* ^0 |1 r  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
6 L; ~. l2 L; {7 X1 \' I
. m* H* X* N; g( l& b5 [7 R9 O  class CWipeImageDlg : public CDialog3 x/ X6 {% `& a! l+ r9 {, d& v

9 k2 [+ m' O$ B, v$ w! k. E  {$ \! d5 h3 g8 \1 n( E$ J

: S2 F# t. p  I2 p. s  // Construction- C- Y" v) ^( y- e, j2 z$ t' ^5 u
0 Q5 w5 C) J! a
  public:' V; s- ^7 {3 r& S6 `) F
. h+ `4 c7 I! X9 s4 a) p" {
   BLENDFUNCTION m_bf;4 r% P/ v/ {) S3 y

5 Z7 z2 `3 @; G$ ~" K  Z# N* D   CBitmap cross,lantern;
. o. q0 ^# h( M. w6 _# T: A  K; E7 X6 d' K* n
   BITMAP bmp;3 i+ `  h5 U, B
5 p" y& ]: J, N. Q
   int bmpWidth,bmpHeight;
' I+ T) N# g% v+ X' ]5 S/ ^, Y/ q9 `) l. f/ Y7 X, l! h+ Y9 \
   CDC dcForCross,dcForLantern;: t0 C, K: r7 K4 _8 s0 L5 a

. E0 }0 A* z4 M% Q- t   CDC? dc;( c, I5 s( U+ h7 b* p
1 C$ S/ I+ b2 M, V
   BOOL bShowLantern;
! q, A! V2 {5 I8 L1 i% x, {& q3 @
; s7 M+ e" G  c. o" W" I0 m  ………(系统自动生成部分)
# O5 }+ |3 ^% ~, A1 M+ i8 n2 V2 g" s$ N9 i- L9 Y" B
  }; . d+ e! j/ q7 i( d

3 H2 f" m, ~+ N7 s+ R! w  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:6 c6 M. x5 h* f  a; i

" o+ r- y) A) Z6 ?( T; \7 A  BOOL CWipeImageDlg::OnInitDialog()7 B7 o4 p) ?1 I. i- g' x2 J3 ]

- `/ }( f: h- D8 D  i$ o  {
. _7 {9 Z8 H* v: y0 [8 p/ i2 d8 {# A/ m4 j* |: E" b' i
  ………(系统自动生成部分)
) [3 s# J) S% F: T, n0 ~7 g3 n6 Y  Y" \. Q
   // TODO: Add extra initialization here& W. z+ O& R6 \2 U/ t
" F& Z: I+ o6 S! G1 T, x
   //初始化全局成员变量* J0 r7 N& ^( z2 T) w
0 n5 e( }* Q: y* _$ h7 l
   this->bShowLantern=TRUE;8 a1 m6 Q( Q7 \& {8 u2 K. g

/ n1 q) X  h4 l  m2 E6 r" Q. A% G- M   m_bf.BlendOp = AC_SRC_OVER;
4 I  T2 c2 N: u4 C" y* E* D& c$ s1 p* B1 Z" G0 k- n
   m_bf.BlendFlags = 0;
3 n5 j- h# ~& T% f. V
1 g. P! A5 y/ V3 I% V6 J   m_bf.SourceConstantAlpha =10;# d$ P  u9 f8 G2 e7 N% X0 l' Z4 w

' o  }5 W: `/ @8 E; h   m_bf.AlphaFormat = 0;
. p* g6 N- `  X  ~5 Z* x0 j+ m* j/ M  P4 f: ]
   //为节约篇幅,以下代码中略去对操作不成功的处理代码0 g/ W) c" H" g4 v! I6 ?
$ ?& o& ?! M& C) k. L
   if(!cross.LoadBitmap(IDB_CROSS)): D: E! n% g3 @/ }9 O
; k. h  w/ ~1 m( s' K
   {
  k; d! i: E  l9 E" C$ T
; g* h: y' d# p5 U, T! Q   AfxMessageBox("装载位图出错!");
& s; n, N- b8 o8 F0 J6 j; \7 R2 ^, r* |9 ~+ s# z% N, h& K) u0 M
   return FALSE;
" F* U4 F9 i9 U  q
. v& @5 z  M" E% i; c$ W   }
0 y: @- ~0 b: _; q% }" l% |( d2 X# t) {: e7 t/ z! j+ @; W; n+ d
   cross.GetBitmap(&&bmp);2 b  r) u2 l! a+ S
; W, }' J" ?( M
   lantern.LoadBitmap(IDB_LANTERN);
) `! J3 E4 b# W. k; o5 V
9 N! n6 e$ N4 u   cross.GetBitmap(&&bmp);
& I: E% H% L( Q8 c# X6 ~% O
0 c1 ^! f4 A) \   //获得位图的大小信息
: e: c4 \' P# O7 K& G/ ~* b& \7 I/ b3 S) b1 [, R0 Y
   bmpWidth=bmp.bmWidth;
( B# l7 e% A3 x% x& a3 W8 X6 i5 m& T
& ^4 u" Q: Y9 ?; {   bmpHeight=bmp.bmHeight;: W  ^9 Q- h6 \5 f3 F. ]

; A& G2 Y5 \$ I: Y1 X  dc=this->GetDC();
! `9 l4 [$ f/ \" C8 E3 }% R. c* D, T* E& [# {: t0 `8 n$ m$ [
   dcForCross.CreateCompatibleDC(dc);
3 f2 V7 J( s1 n# ]( _5 M* `; n* b: d! L3 N  Z
   dcForLantern.CreateCompatibleDC(dc);5 y. M$ ]! M& q1 Y( C9 p4 A$ }! x

, A% H4 p% O1 d8 e4 P  [1 e   //将位图装入设备环境句柄
0 A, k( n) o; C7 @
" s) `( Q/ l4 L, p   dcForCross.SelectObject(&&cross);! X# n7 @( p) F2 I3 }, R) d! A
1 m; J3 W% a! d- Z+ r2 m0 v8 [
   dcForLantern.SelectObject(&&lantern);: ^1 f; T( h! \& V
  M: G% z; e1 V2 Q! r6 p/ v
   //打开计时器* x' {  o1 D  }( `
1 D5 j: l5 c: N& i2 o2 n. T: F
   SetTimer(1000,50,NULL);
2 o9 d, }; J4 @% B" N6 `* K; L5 ^( Y) M
   return TRUE; 3 C0 Z2 h1 F& A+ ^

2 p4 g' E* {7 h2 \, y3 S  }  p7 q( C' K" m7 `" Q; t, _
  B$ k/ v7 w, \3 j
  void CWipeImageDlg::OnTimer(UINT nIDEvent)
, |+ a+ U( [0 W; P: ?
/ l$ m: `5 x: A" T" d" x9 }% ?  {
% U& W& p5 b  a# L  i1 Y3 s" X( n6 f( w
   //图片透明度每次递增5点
) G* m; T) I7 B% N% S3 m- \. b3 w, ~1 m! k
   m_bf.SourceConstantAlpha+=5;
5 R# M; r. _3 n5 T/ e0 e9 B- ^* d$ E9 M/ T  [
   //当第一幅图片完全可见之后,显示另一张图片+ j9 A, T) ]1 L5 a4 ?

' o1 P3 T" I) D  A) ~6 o/ {( P8 N   if(m_bf.SourceConstantAlpha>=200)1 U2 C  `+ }, H+ C/ W0 j

" f, P+ E( R! C" }# N, \  ~: G6 e   {0 r/ E. C5 x/ f' a4 h
5 G) _+ H' @! [0 c
   m_bf.SourceConstantAlpha=10;
6 h. K% Z) ~3 S. g/ i2 M2 T# R6 O, k7 f, G: U& R
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
2 l% d' J7 Y- Y  O5 ]  ?: t+ C& g. t! P/ q3 F
   bShowLantern=!bShowLantern;
9 ~' J/ T: F4 n1 j& k7 A" D$ Y. V5 W- E5 v5 C
   }
' V: W3 }( O  n1 @4 Z5 I$ L) C. l
: ~9 W3 M* e9 J, r7 T% U7 T   if(bShowLantern)
0 S+ ]; p7 d3 x
9 z1 ]8 H3 T  q4 o1 P  m: m3 U   {
4 p5 [9 A2 u3 {% K3 T; W
7 Y" u' E; D) B% ?  n7 T   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);# E& m3 V9 D6 W: ~; l
9 E0 o) {9 }+ l# D" e; c& \3 y
   }9 X- P  R7 {5 G3 \% g& k
6 I3 G, t0 u* y0 m: n! w0 Z" z
   else; k% O5 ~% r$ z/ U4 q; {
. B3 {1 _# m% z1 n5 Y
   {, T$ {: k0 W3 U7 ~- y: W+ h3 u
1 d" N2 t9 d3 P. Z
   //按透明度递增的方式显示“背景”图片
" _% M/ u+ e" D. f2 s" P, z0 O6 G  L# L+ D. ?3 g3 S4 ]3 N
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);7 b2 A2 Q7 l! i

) B: [" \- I) A& j   }) K1 e3 ?" R. L. `- f2 _' z4 ]$ Z. ^; B
8 C$ _- j5 S  C2 ?5 F! F/ g
   CDialog::OnTimer(nIDEvent);/ {! s+ M& D; ~1 U0 X9 |

5 T1 n/ P$ U2 e* j" V# h6 }  }
2 K5 K* B) x# h; P( ~  [$ Y& P6 q2 |, ~# F- ]

8 C" a7 C. U: U8 S& k0 ^# \- M  编译说明1 k, j! K+ l5 J+ |# @% s& p! t4 p

  p2 }% O% N7 R) Y& i" g! _( n2 A3 e& Q
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-15 06:31 , Processed in 0.019438 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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