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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
/ ~7 i6 T" V! {" ~- a/ E" I2 Y1 L$ y( ]
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?  u0 H0 f! y5 m5 I* A5 K
) c( G1 ^0 ]; G/ _0 p( ?

" T2 p9 N% j' [( C  n8 p  解决方案
* P. v3 _8 ^+ ]* [1 W& E% j
, k- i7 j7 o  w, {6 I, b( P! H  r0 |+ z$ J
  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:1 h/ r% _% n1 h/ d
6 c+ P, T  L% i0 m8 [2 n5 \- O$ c
  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red 3 s, ]1 ]9 ^" r% D' E" `
& k# k; s. v" _7 N6 |7 G
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
! ]1 Z* F5 c: ^2 R1 S  R; [6 S! @* M9 |6 q6 V, C4 W
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue+ O; H/ ?5 v7 z4 i: }9 L3 g

: _  u3 ^0 F0 D' @  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' t. L- n! `# R
# u7 H; ~) L- a$ l  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
* e0 c" W- ^& E8 p& I  ?* G! Y
- g! [( H/ H! k. W$ t0 v+ I- l# G4 x+ ?  BOOL AlphaBlend(: N. s( z9 g8 c' n. V

: u8 T8 z: W8 J" f6 C   HDC hdcDest, ; Q/ b/ E( o( H# l9 H, `

! N" `/ r' h& \1 U% ~  // 目标设备环境句柄
. y4 `7 W! Z. z) V1 x
" u1 }9 ?* V; K# A9 v   int nXOriginDest,
! A9 |9 J. x2 x* I, G5 X
0 v  _& F& P) T8 m6 P8 M7 @" Q9 x  // 目标坐标x
* H: d+ s( l1 W6 Y: o: k+ ]! D% {
   int nYOriginDest,
/ c! u! a7 F% `( \8 k4 s/ B6 C* A( b
  // 目标坐标y$ J# I+ m% }4 w3 P4 y" X7 G
: Y) {  Q* r# f8 j6 C) g! r7 L
   int nWidthDest,
# x/ ?3 l) {: x. G% g+ k, D7 @9 n/ b8 V& x
  // 目标宽度0 J# y9 j- t. ~* `# |# C' d0 X
. L1 `4 B& L: o
   int nHeightDest, 0 F7 V) E) I$ H; q. g
! m6 [5 \1 |) f
  //目标高度6 r. @1 R1 ]- m; t( O$ I
; X- h6 H' M+ G: v  ?* h, L# ?
   HDC hdcSrc,
& G' I8 d1 y* L( O
( X8 i$ D! D  l; s  //源设备环境句柄
" A3 U0 v& J0 n, `7 k4 S) A0 V: r( {7 J( y
   int nXOriginSrc,
, u+ \& S& h- V, }+ y3 n1 y
3 Z+ n( j% h( L  // 源坐标x" l9 m0 ?  G- _" b1 j' n! t# v

1 E+ U) y+ i8 B6 G7 O5 j6 h   int nYOriginSrc,
$ W' Z& |3 C2 z; {* a# U. n1 C' ?7 i, v+ g8 ?- s
  // 源坐标y
0 G2 L  e& B4 B6 M% a1 Q% K$ i  g4 s$ e2 f' F, e3 T
   int nWidthSrc,
% Q4 z1 ]' @: b2 R6 o6 ]6 J8 i! g" r& E$ l& t$ V
  //源宽度; ^2 t: J0 X6 g, z/ G6 w

1 a1 A2 e: G! B- b2 j   int nHeightSrc,
+ [: s( U/ j; O2 `. B+ c* j* Z; N5 o% L! c  |  }' L
  //源高度+ o5 Z) c  x% I) s4 b  j& I
4 J- M6 j& B* j. H5 d! s
   BLENDFUNCTION blendFunction
- w8 _# {4 B9 \& R* J6 v1 |
% \' l2 V6 F5 _8 [  // 合成方式具体数据结构
4 G6 ~, G9 u: @- O8 H: D$ A( a
9 q/ J. h  K4 ?; K& D  );
# H, W, H# |. P# W# L' t/ m
5 I4 v' j9 ^+ G8 O* M6 r& B0 h* l  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
9 n# F$ T- D* S; q( \3 H% w0 g" @6 I7 o: L
  typedef struct _BLENDFUNCTION {' J% w$ D0 N: s! |9 K9 f( [: O
! f. @* v4 z7 h" z
   BYTE BlendOp;
5 v3 G8 n: H' }) o: Z8 I4 N2 ?+ A9 P0 F1 ?" K
   BYTE BlendFlags;//必须为零6 G$ ^, |4 R* G4 E& R/ ?# U
4 J) v9 t, r. E9 T5 L3 o( U
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示" D8 j- b# {5 \* m
& ]" f4 W/ L/ u5 h5 Y
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
# J+ Z1 G+ ]2 V# Y3 n0 `/ Q' O, v2 ?/ [, e$ N4 ]( Y* u6 y2 Y* i
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;4 [3 a4 E  U6 y

3 B# F4 ~) {! T3 t7 V2 M# Z  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
. |5 f& j8 B/ F5 J1 W% l6 v1 H1 l, }, P2 N* R6 y

% r0 U- T! Q4 N( {" r  编程实现) B6 y" K' I4 m1 `2 N4 L

" H7 X5 O# k" A* r* _& z
1 R) l. ]# ?) k2 q  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
( [2 ?) d( X! R! ?: `6 }7 i0 `. |# u7 e* i! s+ O6 u7 v' q- y5 y
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
8 T5 P: f4 k$ e5 ]. c, [2 w7 x+ h7 C
  class CWipeImageDlg : public CDialog
: _# z- I- K- r7 O& Q7 g. y( y' E+ k% K; q' C+ \8 u* S) }
  {
( G1 @/ A" W2 W4 v; E: d( L7 h" U* \. i& L
  // Construction
9 V0 {7 }; _2 t! f, S7 J$ B. {4 P" d/ G
  public:: O' [& Q2 D) Z+ Z  \$ a
# b9 u6 \' Y! ]1 n9 Z
   BLENDFUNCTION m_bf;4 q2 V/ t$ n0 s( [; G; L$ @

9 q, _. Y" i* h   CBitmap cross,lantern;, m2 i, F, t/ e* y7 a7 [
, @1 j, O7 s1 x" J3 e
   BITMAP bmp;) ~" ~. n, `1 {! R* m2 w4 V

: k  z7 C/ x0 M/ {   int bmpWidth,bmpHeight;. w' E8 C0 }2 c  S# |
6 w" @, v! Q% a5 G
   CDC dcForCross,dcForLantern;- {# V2 P* j- w
$ u* V! n/ J" H" [
   CDC? dc;$ J* R& ^' U3 t) k
9 `8 f; g' n# p
   BOOL bShowLantern;
, h: m5 G# X7 b6 U2 c% r- k
% B0 J/ Q6 T2 a2 ~+ [  ~9 R  ………(系统自动生成部分): ~: p3 B3 U" e% N
) K& u' d7 E" Q# M  ~" o, L
  };   p& z. L; `  ?

# G4 |5 v7 a. q3 a  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:, f5 v$ i7 G" p4 i6 G& |7 m
6 y1 v$ ~: A" F* a1 R- z0 H0 c% F
  BOOL CWipeImageDlg::OnInitDialog()+ M  p: f& q+ D: y  S
% U8 e5 r  g* E( I+ A7 {& i; U
  {
$ a  o3 h' I/ q5 q; n' H& s
# T  v: _7 u$ d1 i# {; r  ………(系统自动生成部分)
/ E' N6 J6 ]6 Z% n/ W, m- W9 }# f6 U- k+ n2 {: x
   // TODO: Add extra initialization here, D1 I) S( k4 t/ R' F0 O8 D

& n& k" Y7 M1 m  l- r- R   //初始化全局成员变量, i8 Y# M, }# U! z
- Y. D. O! z( W* k4 u& _
   this->bShowLantern=TRUE;8 ^" D  T4 k9 {* r$ A( ]

: `( W# o1 @2 ~) S3 Q) f   m_bf.BlendOp = AC_SRC_OVER;
. C; X# e$ u% M+ q% h/ @8 V7 }
: g+ i3 x3 y) @4 g4 i   m_bf.BlendFlags = 0;$ c, H5 ]1 T+ `$ X" [
" a- e6 U7 v8 q2 g4 [" k# T8 P4 U
   m_bf.SourceConstantAlpha =10;9 Q# s$ E3 F( @* [  p! W1 W3 ~! d0 D/ V
& z/ Q+ V7 n/ V9 I* c
   m_bf.AlphaFormat = 0;
7 Y0 p$ `0 Q' [& ~. z& u
7 f- o# Y* o( v6 W$ W7 t+ l   //为节约篇幅,以下代码中略去对操作不成功的处理代码
! a& ^- r8 M5 J( c* R! y) n: b1 [4 B; K' Z2 Z  t2 h$ {
   if(!cross.LoadBitmap(IDB_CROSS))
" P* I. O5 y- K, _! N6 |2 H# g5 g3 j$ Z' |: i, X
   {# G* H( ?* I; k2 m5 `' ?$ r
, Y  x4 o4 x  f0 Y! e1 \! ]1 l$ |
   AfxMessageBox("装载位图出错!");
, F5 o9 c) e% s8 K, L& c
: g- t: H9 S; o   return FALSE;: `1 f$ M3 ~+ ]: Q* o. F9 m: o% y
" }( C- u$ U" z" @0 Y, x5 k8 N& |
   }, [4 N& k, w: c, A( ]3 n

1 S- r! ]4 B: g( W: A7 l   cross.GetBitmap(&&bmp);
/ V# l; i" T; n7 O/ t& ]7 y' x- _
, o7 z8 ~1 P7 H2 {) n   lantern.LoadBitmap(IDB_LANTERN);
4 [/ W7 T% ^* Z# f" C! g4 a  H% u7 G) h1 ]
   cross.GetBitmap(&&bmp);# }/ u  h. d, O8 l) A, O2 N  _( [3 p
% o2 D1 W8 g7 {* A1 m7 _2 i$ v
   //获得位图的大小信息
6 f( y/ N3 Y4 A6 j$ r/ _) T& G$ ?) h( i' `
   bmpWidth=bmp.bmWidth;) ~( c5 E: f- z8 `7 O0 ]8 _2 p- d

7 m: E2 P' g$ F( ]$ p5 s3 k   bmpHeight=bmp.bmHeight;
% }5 i3 V5 n9 t4 y
% k+ Y4 J9 u/ k# y3 L' f  dc=this->GetDC();- O! P5 L8 j5 i( u$ o
. s3 m  A( T2 I- f% X" t4 g2 W9 X
   dcForCross.CreateCompatibleDC(dc);
; `' L9 H/ _' A# J9 O7 z
4 X! `* G* Y! }: J1 y) X2 J   dcForLantern.CreateCompatibleDC(dc);
) _# K0 i+ K' z2 I$ [# r/ J$ Q$ d- d/ W; ~3 i: Q
   //将位图装入设备环境句柄. t( T. p+ s/ G7 ?. G( @+ j9 Z

4 K2 n# A7 m9 i+ |, K. [   dcForCross.SelectObject(&&cross);  R' h9 m( e4 `
) a5 ?6 X0 N  c+ g' b" m
   dcForLantern.SelectObject(&&lantern);
( Y, z- f+ j8 E* x- k) l" w( Y( y: }* R
   //打开计时器+ u% y. a+ w1 F# P4 K) y

: y4 s9 t' J& Y$ ^' j1 |   SetTimer(1000,50,NULL);! q/ I, U9 Q6 }( {5 V

! |. R* f0 c3 s* G: a5 N+ n   return TRUE; ' X  I! F1 L% V, T$ i* M  s
! q6 e( [3 E' I9 O% e
  }
) z, F/ o: Y$ c2 @* C$ R
* V/ j4 u7 x0 O( x  void CWipeImageDlg::OnTimer(UINT nIDEvent)( K3 o/ x5 \' M% `) p
: o9 m+ L! n' f( ~8 J
  {8 u: }# G" _1 a# K: e5 V5 U9 {
( ~! Z& t6 r, G# X
   //图片透明度每次递增5点
0 j2 E5 o9 y" Y6 }' d1 u1 B1 y. J$ a, c
   m_bf.SourceConstantAlpha+=5;; X5 a  L' f. s! o3 k6 `8 v

7 K5 b+ Q8 g/ x/ }; ^. J9 A9 }   //当第一幅图片完全可见之后,显示另一张图片
# A! {/ k0 g+ D+ Y6 i  g! F; X, B: n. O: g
   if(m_bf.SourceConstantAlpha>=200)  N: f' b: f" S8 x' H& a, h3 o

! j! H& {( G- j. P+ J6 T0 C   {3 Q3 f2 R6 \1 V$ @4 V
" x' ?- S& F( o5 f. }* l* ]6 l; a; \
   m_bf.SourceConstantAlpha=10;- v8 l5 w( n) N1 y, i' I
/ T$ b1 `7 `/ Y& S( ~( c
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
! M3 f5 {7 j/ D4 u& X- b8 n  q9 r# }/ q3 W$ f; X
   bShowLantern=!bShowLantern;
, }( m3 P- \3 @# d' l; U0 t/ m& o( M) F! @$ j
   }; b2 b- g" I' g+ c1 g) D
4 B' k8 ]4 G! J4 o& W( k
   if(bShowLantern)
3 j9 C0 P& q: x2 v* y1 q
4 u7 a! J! D$ J7 H  w   {9 t, O! q1 ^0 f9 q2 R

1 O% n7 ], V2 ^% d% a- T! O5 |5 I6 X   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);0 o; i9 K  d6 }+ n

5 d- j# N" y1 _9 `   }
2 R8 M" Z9 p( C* n/ m% z
; L+ C5 s" y& [0 Q8 R   else1 i$ ]4 M: {. F. t' [9 m
1 s+ ?+ x6 I2 h2 [% W
   {, a# j; s2 C5 Z  v

! @- \' H; S* p; J   //按透明度递增的方式显示“背景”图片4 A) X) i/ z: T, s

  q; e5 a4 L& U5 G" C   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
5 ~" M( i% K1 c* N- Q8 O& Y3 L3 N% w! n  Y8 C! y" c
   }- j" m: t' X+ l- h& Y) Z5 t- j
2 X9 U- e+ E5 [1 z
   CDialog::OnTimer(nIDEvent);0 t0 Q' h1 o" u2 E

5 c7 k7 ~6 X3 E$ J: k7 t( }; I  }5 R6 C0 U4 j  h0 e# }) y
( C, N3 t' g, \, S' L
1 u2 [8 q2 P6 a+ Q4 c/ p- ~  {3 i
  编译说明
3 _. D/ M1 M+ }  ?1 v  Y' {
2 M7 \" Y' A" w8 Y
, }; ^- W1 W0 v" z0 |3 [  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-6-19 19:22 , Processed in 0.035228 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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