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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬 . i; R4 l& W& i, o+ k' c
" ~' e; h6 L' k' \- m! d, @
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
+ |6 P. o  y1 F' X3 x8 ?* c( n, D- J, }( n# d+ Y

% ~6 K, r9 I1 V8 n8 X  解决方案
( C+ q7 k+ o8 F/ J6 U# a: D1 F4 n8 J9 l4 B4 O. T& Q0 i: P

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

$ v" l! e* g- G2 W  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red . E! m! U/ K& y9 S6 Z
) p8 Q' W7 D, W4 v) U, V* s, z
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
% Y- J; h- b6 t" ]( S8 o4 f* a2 n/ b$ N) w6 ?1 Q2 e
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
4 H# U5 s& \- T  I# m/ X6 i" V) A* J6 A% V8 b. [) r
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。$ d7 L/ x/ S, m) A. B; p9 `: \/ H
' J4 j4 p- `9 c- ^  e  x$ s
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
6 B$ r* C% @% e* I8 o# K* s8 z7 h  C4 ?
  BOOL AlphaBlend(5 {; p' f0 @& z, e# _
) n' U" _' L- q9 e- d
   HDC hdcDest,
  k* T9 ?1 R( N# a3 I; {& L
4 l5 ~9 r" G' h8 j3 }# C# k# i  // 目标设备环境句柄
; K+ o$ L3 Q' \. B4 ?; D; a, b; _! ~) e# ^+ e7 P1 T  a
   int nXOriginDest, 7 b( g; Z) T4 q* \* S6 u2 Y) S6 u

" Q. H9 g3 b% }+ m  // 目标坐标x
8 \0 B) ?& t0 Y: Y, [+ D3 K9 T* x* F$ a. V1 J, ]: P
   int nYOriginDest, 8 _* i0 {3 t6 J* k- L: t4 `" }. l
- J# n% P* S" Y
  // 目标坐标y) w# O5 [8 P4 T3 X( m* P% k
1 @7 [$ A" Z9 _& O' l
   int nWidthDest,
, f/ m9 d- O! u: j
/ a+ U3 P  t* ?, G: ]3 z' z6 N* Y( R  // 目标宽度4 V% r: G3 D+ x/ E2 w6 R* o, O

- I/ N# s: y* ~/ E   int nHeightDest, / ~1 T; Z; g: l  \

5 U1 l3 B# H# F- ^% Q: M/ D  //目标高度
: |4 h8 F6 _( R6 E( z) f. _1 c; I% ~4 Z
   HDC hdcSrc, 9 q, X# s# W. R" k/ N

: T1 H& n. Z% o0 x0 h) @  //源设备环境句柄& w( U; a  {  I# }! @% S1 t

, [8 r% E$ J3 N! F- M* Z8 E   int nXOriginSrc,
( f+ O* }& e; H$ c6 S7 N: D9 z5 k& u
& W% m8 d2 W, V& p5 f3 O9 H  // 源坐标x, O/ J0 d$ Q% h+ Z9 j  `( t
0 T. w0 C; ]: \! v4 k0 M- J
   int nYOriginSrc, ' c8 [$ F1 X7 ]6 e: c1 p) i

" K5 \5 p* e" K& O: h( S  // 源坐标y
" c& k) e3 G5 O9 x& V7 y6 m6 F) k+ A. b5 i
   int nWidthSrc, 4 J: L7 h$ F# X8 j% S2 e
+ m: R! l6 N) @* N% Q. }# i/ D$ ^
  //源宽度3 ^+ P* \3 K& F% E$ V! R9 Q
4 C3 W1 a0 n  O/ h- y9 ]1 X* T& ]1 q
   int nHeightSrc,
7 c- D6 U1 {4 Y$ d: M3 O9 H2 N) e$ a( c7 C  |# f
  //源高度
+ }/ t7 E6 i' M1 t9 b9 u; t) E1 \. b: n" q* I3 u
   BLENDFUNCTION blendFunction : ?4 g! S+ |' N$ b; A1 L
! g1 A8 X, t/ `' @$ m& E' w2 q* ]
  // 合成方式具体数据结构6 N. C) m9 G* ~$ L& B
. r1 h/ {' z3 v' q* ^+ q
  );, t- W7 M& i* ~; r9 o
% V4 c8 V! @. v  E) u
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:) n3 E% E# Z: ^7 ~8 F
% S+ j$ a8 F7 o- P
  typedef struct _BLENDFUNCTION {- z6 p5 E& I- l+ b7 ?. f9 k

. z( `6 y! F- x8 c  G0 }0 N! t' e   BYTE BlendOp;' P/ z+ z2 l) Z0 H
: I; L4 C% U. S* c8 k# C% z
   BYTE BlendFlags;//必须为零' p/ u1 Z% b" P' j' H
0 D, O6 e8 C: B+ W. L& u5 s. ?  r
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
4 ]! s+ t7 \- r$ x2 M- F1 H# P' ]
6 s& H: n9 A1 C   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
6 y1 O! G, q* D% o) r  K# O9 D& r( v6 |% T
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;$ `; N5 z  L( r2 h

" ~: U" u5 T8 D4 r# h+ m8 _% N+ C  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。  @) J: _" s4 ^6 c  O) x

  J5 i" y; ^. d0 N/ R: ?: C
% \+ t4 o; o0 l; R  编程实现
) Z8 v  E! o5 t- h3 @1 m( m, R+ u' E% B0 d
3 J1 [  x2 u* v; r+ w
  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。! t4 K6 P: K* c

/ F: T, a! _3 I; r  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:# r. q4 Y  s. _+ S9 [4 X, I5 u

' f; j) G+ s- {% P7 h6 H+ I  class CWipeImageDlg : public CDialog
. ~# ]3 N/ `$ c
2 Z% h" k8 f  L% S  {
4 @0 ?) ?$ m( x1 S! E' V- X
6 ]6 l( q- {: j5 e" ]  // Construction0 D5 h+ Z( A  A! P/ }
$ o7 D, @; E/ \
  public:
7 C5 p& c5 _6 r0 m% A8 c
9 J! B& u2 P  ]& Q+ h   BLENDFUNCTION m_bf;8 q/ e: R, F4 O( y& o' d

, Z/ @5 ?7 A, f9 G) T  u   CBitmap cross,lantern;7 O8 H8 V: o5 L5 ~6 i

  y( X( W0 x0 F   BITMAP bmp;
! f( _) ~2 X: |  M* H* Y
, b; x$ r) d' d' o$ z9 s# F: D   int bmpWidth,bmpHeight;
( {: {. {( q* G; [' B6 j* a% b
, `6 n8 `9 s- v! m9 k& }   CDC dcForCross,dcForLantern;# v6 p7 C4 U# F, k8 l/ q6 Y2 \
: P0 l( K6 w7 A
   CDC? dc;# M7 `3 J+ q! O1 j% }
8 k* q0 Y+ ~$ `9 [; H% S$ U
   BOOL bShowLantern;# H: c% M& O8 s) N! |
$ `7 a2 |" A2 Q) N; T
  ………(系统自动生成部分)2 R) V6 f( j8 I
+ q8 f( A, P) H& e* j
  }; 6 }" e6 T+ G, I7 _

: s! m1 Q4 m: l) S- s" n  U  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
. E: Q& H8 t  o# Y
  P8 S: s; D+ e% S& G4 [  BOOL CWipeImageDlg::OnInitDialog()4 A" E- }! j( A7 X

: T6 @. B- k. K7 ~  {5 H6 E9 L4 x  f5 k4 d
7 F- q3 y- H7 d4 b8 h
  ………(系统自动生成部分)
( |  r% J4 w3 K8 Q8 T9 Y( X- m
   // TODO: Add extra initialization here
8 o6 K# B# j% S  S* {- h9 R
) G$ h5 A" Q8 {$ r) S2 W1 h3 l   //初始化全局成员变量( I6 Q1 g" r. z( m3 _7 e, G- h8 c

( ?) n2 f% L& \( o8 J   this->bShowLantern=TRUE;5 f3 Z% d, {  [9 \

4 ^. D. q0 I7 V! p   m_bf.BlendOp = AC_SRC_OVER;
6 Z. F/ z: w8 c% n; t  h5 F8 ^) h7 o- n
   m_bf.BlendFlags = 0;. m# W- Z0 h* Z4 O) [$ V6 Z6 H

; s. g% O6 ~7 J2 ]6 l3 l) `$ N' S3 j   m_bf.SourceConstantAlpha =10;
1 k9 b, I, U5 \4 ^8 y
4 ^, U' J' J3 ?9 g2 B3 E2 z   m_bf.AlphaFormat = 0;
6 B# @' o' T$ W1 i" q; k. M6 R
! d6 c+ w9 n) D9 U! i   //为节约篇幅,以下代码中略去对操作不成功的处理代码& y$ }8 ^0 U0 h* |
" n2 s( T$ g6 B, Y4 A- K+ g& g4 }, r
   if(!cross.LoadBitmap(IDB_CROSS))
: g% |  i8 V+ @
; g9 e* x: V# {1 P   {
) {+ T  `" M, e* [8 z$ r. e  l' [2 K9 ~. a
   AfxMessageBox("装载位图出错!");
3 k8 ?+ o1 t- o7 ?0 z* Q. }
2 G  ^2 S3 t. D. |   return FALSE;
( b+ s' y. G% \- t. ~) f2 _; @  G" j- N% |/ Y: C8 C
   }% }5 F" l' v# ^% N9 s5 _
! W$ `+ j* c3 g$ d2 p
   cross.GetBitmap(&&bmp);, i: _5 K( c. S  h
7 E/ d* d9 ]9 K8 U: J
   lantern.LoadBitmap(IDB_LANTERN);* s: d% m. B2 i+ R& A

* f0 @( t5 l: a# G9 `  X9 V   cross.GetBitmap(&&bmp);
! h' S# C2 f& k  o9 ~; k. u6 \
5 Z- J1 t& J  m  J1 I* z& x8 C- m$ e   //获得位图的大小信息. o9 Z  ?) f5 @# y7 y4 M6 o4 {

" }' [# X% T% a; ?   bmpWidth=bmp.bmWidth;
" y! C% j5 Z7 k  z6 \3 R, L/ Z
9 W6 `8 X: [; Y% S   bmpHeight=bmp.bmHeight;/ {. P" o7 F" ^7 R) v# k5 J
( Y# B1 E/ V* u
  dc=this->GetDC();- b  U" \" k, i2 }
! }3 F) y. R0 x5 ^* L
   dcForCross.CreateCompatibleDC(dc);! u( O2 I0 B- T1 F* I

+ X; V/ O2 C6 E$ d9 A   dcForLantern.CreateCompatibleDC(dc);
- o+ w# V1 m6 g/ ~, J7 H3 D
1 I3 ]: K( E. ~  B   //将位图装入设备环境句柄; \& R' O3 i1 X( y+ p3 n; y4 _

# o# i" {& F( A   dcForCross.SelectObject(&&cross);
9 P, a  m) O6 Y0 U& v4 E
7 c- N  d/ v9 h   dcForLantern.SelectObject(&&lantern);2 ]- Q3 f5 R, g- @  x: d

$ v+ m0 D8 j! x2 k" i( B   //打开计时器$ k9 D1 f0 g. U0 l+ X+ S! `# R
2 S9 B; y$ C6 i2 m/ C
   SetTimer(1000,50,NULL);
/ F+ Z. d7 ]% u2 Z6 d- N. p1 h) t/ a
   return TRUE;
! A6 V2 Y* m, ?- y* K" z) L1 \( j# o/ B9 w8 h) }
  }
0 l. [( D( f4 O: s* Z( U8 `0 e
, ^9 ^) O) I4 r6 _8 W8 L" @  void CWipeImageDlg::OnTimer(UINT nIDEvent): E8 d. s3 [. e+ X5 T+ `
$ d6 W/ a! w3 s; g/ k$ Z" I
  {; n! l0 r. E; A' @4 G2 R

8 W9 y! \2 V9 T: y* _+ j# X   //图片透明度每次递增5点5 G  h! s5 O. ?: b. b' A, g3 U: d

( B8 J) m9 D  G% K  I9 `5 d( D   m_bf.SourceConstantAlpha+=5;
2 n/ g% O# \% F$ S  v+ x% U: g7 G/ r8 g
   //当第一幅图片完全可见之后,显示另一张图片% H' Y$ n& h& H6 A" E' }2 j
5 I. J$ b$ w5 ?  j. P8 b
   if(m_bf.SourceConstantAlpha>=200)9 `$ n# T. l$ C/ O+ n- i; w

# e6 x+ t" Z6 ?$ n$ _   {
# v+ j' ~" [9 e6 K$ W
+ F7 q) @1 ]4 S) v& |8 V   m_bf.SourceConstantAlpha=10;
9 F# T+ S% Y/ z$ L1 K/ G3 ]0 o
; O& z) c% U! l5 i/ v   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
- J0 i$ x1 j& A8 B2 x5 w. s
' H8 D) u/ l0 Q) s) P) M  x   bShowLantern=!bShowLantern;3 q) b% X0 ^: j/ c/ r
" n. B4 g/ h) t" Z
   }
( {! ]& v4 K  l) i5 V) _" W) i* q6 X* p' w9 O, r2 e. Z
   if(bShowLantern)* _# y6 ]; y. r! U  S  L! E

* _9 ^/ y3 k  k) v1 O! s   {
4 S, v. O+ o% v) o* }) \0 w
. s/ H4 F. W5 a   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
+ u" |0 K% ?3 c& u5 K7 ]5 N8 p7 Y/ y9 v1 F! y( @9 Y
   }* i0 ~2 }! u  r0 t, g
1 I3 M. V; R, r! \; K
   else5 j: o8 z# L  {0 M- L( t- M2 R
- g5 \' G3 n# a  G) K8 o7 b; T9 m* o
   {7 L6 N$ b" [2 Y9 n5 r

! j. Q' C# }% Q, ^0 V   //按透明度递增的方式显示“背景”图片5 j2 [; w5 `4 h6 z# F! h' {
& d; X. T, P  Z! ^4 t- K5 i9 V3 ~
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
+ a; B2 G# n% [4 u4 \2 k+ x4 Q6 I, r- S9 `  ?
   }
: H5 P" N. F2 E9 p
7 D. \* O- P! o  L% t$ w   CDialog::OnTimer(nIDEvent);
: z6 o" [( m2 U" p9 t2 A8 D1 q; M/ m7 {% n0 O1 t8 b& V5 F# @
  }
2 Y! j  j9 N8 ^
$ w: G& l0 z: A; j% i% Z0 n
7 Y% f$ v# r$ l8 m1 a* G  e9 {  编译说明% ?" s7 Z  i+ R$ H

4 g" g* e% [* w: x' }" Y
, j8 [+ ], K! f% \6 J. {  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-20 16:30 , Processed in 0.018528 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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