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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
: G4 B6 L( z- d) g% |/ k
' v( z& G) ^9 P1 v  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?$ s; v7 Q6 V9 Y4 }; l! Z1 a
# w$ K" \3 z# k! t
9 j+ f" E# S  Z4 ^
  解决方案
1 t: \' `% L# }5 q: z) g' l# Y4 v( ?4 Y
9 H5 ?! v/ [  \9 P+ D3 D
  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:
  a3 O: _" R3 u  ?
* Z- E. H. k4 A* \# B/ I  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
5 y' d4 f* E3 \- q" O5 S- V% Y* W( V& a) J
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green ( w3 Y4 w% L1 g3 ]; |  U1 v( P

; e6 m3 }1 x. u  j6 u  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
/ B! j% c* R" c' e. W
( L8 H  F1 G7 Y1 ]8 ~  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。3 X% _) P4 n8 s6 ~2 O6 n! S
9 \) p. M4 }( U1 l: C% }
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:/ I& T. Q1 n) I5 X/ T

7 N& p; n/ V" K2 L0 L. k4 g  BOOL AlphaBlend(. `# D% m+ K6 q* @$ B/ \

2 P9 a# h+ m, w" c& I* b# V, S6 `   HDC hdcDest, ; c' [2 a) A2 ~+ Y  ]
6 t0 q! T8 i$ W8 K  {1 M% w+ H
  // 目标设备环境句柄
- W3 H" U$ v5 C( u5 s' a  w2 |. j; j9 b& Z$ Y5 v% r/ a  m
   int nXOriginDest, 3 Q, j* h  v# f) `" u( }. o
& n2 B# t6 @; G6 g% p- Z4 p
  // 目标坐标x
# x  P* M! ]% u/ \! x- j  }' u  P  e& K, _
   int nYOriginDest,
% B3 j( x) n# L5 ^
5 q5 C2 @- b1 X! D$ v  // 目标坐标y. G. H% E/ h9 x, e. V9 \, S
' A1 ~* \4 _  x" ^$ O
   int nWidthDest,
3 `: ^4 G0 Z, [* ~* E8 a. {8 @0 d, G8 f) P
  // 目标宽度* }+ ^  }) v' N! V9 g* a
6 X" w6 {( c/ f% b9 C
   int nHeightDest, 2 I" r, \& x. t1 L
# Q: C. h1 ]1 m8 U. J# m
  //目标高度
# u3 f- p. K, O( n, j3 |0 V% i0 k0 A/ P
   HDC hdcSrc,
; d# E% d1 l  d6 r6 f! J) y  z3 \- l8 {% m
  //源设备环境句柄
5 C: u$ O' a' N+ P' T
: y; P; [' z/ i   int nXOriginSrc,
& L/ Q6 N  |+ X. W! I& n5 N
2 N7 k* a" R" w5 |: Y  // 源坐标x
6 R) _5 s. M6 C0 y1 `% C) }
; {, ^) Y; y( {   int nYOriginSrc,
# S' r8 O1 f. `1 }
. ^6 w0 G: @2 a9 B" ?: o2 @  // 源坐标y
6 W# S9 P; @- a) Y% `% d# n$ t/ i% x& R, Y) N
   int nWidthSrc, 7 L: [8 e, Z4 |$ ]! ]$ H
# F2 D) y% ~, Y8 F8 H
  //源宽度
( }' T2 a( N% D7 h
5 r: B' s: N  Y. x   int nHeightSrc,
4 _+ Z$ Q% U  `$ M
- W0 _: M7 i; _4 n* K$ z  //源高度5 v1 K; B/ W+ r* c, @
  h0 K$ h5 X& t/ p2 F1 o
   BLENDFUNCTION blendFunction # e0 f/ ]: c3 y( Q
. c# q; s: _1 H; z
  // 合成方式具体数据结构- L/ I8 ]0 M* n5 P

% A9 C+ H2 T. i, \3 Z& l  );
- c( D1 w! s: H& e2 Q) J& U# k) k5 v, o- H8 N
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
+ f" U2 Q) [& S6 y+ o, T
" i  P2 G  A5 ~2 I$ z  typedef struct _BLENDFUNCTION {; _0 Z/ c- A2 r) ?/ c
6 i+ R* ~# P9 v& j; F
   BYTE BlendOp;
: Q  i# h0 }2 l" C" B$ |; E
" y; a  l& ~5 @- y7 {) Y+ j: e   BYTE BlendFlags;//必须为零0 r2 i2 a' }. R$ M  Y
# z2 N" @7 |: b/ G& n8 y3 w
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
/ E6 f# }+ l5 v# J3 @/ f
9 A1 e! C& C$ d6 ~+ |   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
& |/ \) L$ f" t; ~/ ^& R. i/ R. J# {
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;& R5 F( X( [2 {7 v3 t; x& Z+ ?
( U+ }8 ?1 j7 s9 p- ^+ W6 _+ a
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。6 j0 D- Y0 e* K- ^" h; m$ k4 J# Y
" V& h0 |3 P/ N  K5 @2 k( L% b

$ k3 U6 u0 t. N' n5 M  ]  编程实现5 j) g3 i' c8 X/ K+ W, p1 A: C
4 D  O2 q: W1 e4 j$ i0 K6 s

+ D, ?# n& y* w1 K* i  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
, }* W8 I9 c! b7 J6 J% Q5 i+ k& m  B; I# v. _% K5 Y
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:4 d, g5 ~' R) o/ q8 l' S: {( S6 N, b
8 i: C3 i/ l0 U
  class CWipeImageDlg : public CDialog
' b' A* t) V. Z0 ~3 y
6 e2 j! c) t9 E/ _' {+ t9 l  {
+ o+ q+ h' g- E4 ?+ m! u* m
; d- B% u# V, `' h6 v8 B  B  // Construction+ ], |) _% z8 @& m

/ b3 y) O% O! u* R# ^  public:/ [, Z+ ~; ]+ Y" y+ R; s
4 A- u0 a$ {# I- f3 i) F
   BLENDFUNCTION m_bf;; c; L3 l8 i0 g: P  z5 e

5 C4 j5 s8 U) b( v8 `   CBitmap cross,lantern;  o# Q) k9 l5 S/ d) P5 J# ^3 F3 t

/ b/ A  S0 J$ m+ E   BITMAP bmp;* X8 A( Z( H; E6 I3 {% R& h* R

8 {# Q! i; _- f( m. L   int bmpWidth,bmpHeight;
+ w4 v  R" Z! ~3 V- b, w0 {# n
% q/ J/ i8 D5 g( p   CDC dcForCross,dcForLantern;* `6 p9 X! h- f% R1 P! y1 y

0 g* k9 J$ y0 C2 R# `   CDC? dc;
6 l9 n0 L8 h$ K3 l6 o) l
+ `) E7 u/ L  o3 g8 C0 }   BOOL bShowLantern;
- e/ Z! h0 G4 c4 @. I. S6 t) E$ v' d3 X6 \# B
  ………(系统自动生成部分)
# G0 `/ V8 h! c4 L  M
' w$ [4 K! Z7 B$ ^- F/ E* i  }; 
8 ~/ k  R4 x- q1 w8 y9 |4 K5 m
- V; |; ]9 y, L! K# X6 e) E/ Z  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:) [9 k6 H  O( a# U3 e
0 Y( F! N! U2 P6 r. b2 M  ?
  BOOL CWipeImageDlg::OnInitDialog()) N. L, V  S+ `# v; \% w
4 f3 H8 t# n& Q- i6 @# J) a
  {
3 m3 C0 {6 Q, i) }' e
% b2 [* _9 |1 E7 D* q  ………(系统自动生成部分), a4 c+ c' w: T5 P: j9 w: s' D: D

3 P% u8 e2 L0 N! m   // TODO: Add extra initialization here
4 Q5 q  u/ T; x. v$ {+ S$ d& T/ o6 |- e9 X8 t9 n5 F( p
   //初始化全局成员变量& |  e5 S. [5 {8 N8 B

$ {! q3 S* r+ i5 n. N   this->bShowLantern=TRUE;1 o2 w4 M/ a% ^* z, n! U" k
1 C5 ?/ N5 F3 Q& }2 b$ p& R
   m_bf.BlendOp = AC_SRC_OVER;1 T3 P1 s2 D/ O3 P
& @6 @: i* H- N6 z0 x+ R
   m_bf.BlendFlags = 0;
" q; I( z! |) g( x: ]# X9 D' D( S- u2 L0 k
   m_bf.SourceConstantAlpha =10;; C# g% ]4 J' C8 ^

  {$ p- _- c! o2 S+ I6 t% y: j   m_bf.AlphaFormat = 0;% L. q1 r: {1 D; p% T3 u2 R

/ j2 l2 D/ P+ C: Q% T   //为节约篇幅,以下代码中略去对操作不成功的处理代码, L( a# F, w- ?9 i& E

5 R  m- y6 M, B- p9 n9 Y   if(!cross.LoadBitmap(IDB_CROSS)): J- j3 J4 O0 S0 S
% W; m6 _. ]- g
   {
+ ?0 p- y6 {1 Q  P/ L6 ^
$ x, w. X1 s* E   AfxMessageBox("装载位图出错!");0 s, _" y6 }- L& x5 \9 R9 m
8 t! e0 D' T% l  A' Q. _* C
   return FALSE;
5 h7 l5 v" F% C9 D0 [$ v9 z& M- Z' B; T! {, i/ C( O7 x$ I/ a" ]
   }
! A. d) u/ v7 q) s1 ~  I: `' Q; V" X& y$ a
   cross.GetBitmap(&&bmp);% s/ b2 R- ^# m# t# H1 `

+ A9 o( b+ b. G8 H# w   lantern.LoadBitmap(IDB_LANTERN);
5 Q5 |# U! J, u
1 e+ ?; d1 L: y* U   cross.GetBitmap(&&bmp);4 u6 _9 e) m$ l+ v3 t
* T& \3 E) c& N) e7 e2 G
   //获得位图的大小信息
3 `! l* Y0 E* H7 d) a2 Y+ c" S1 }! }  u) m! I- x9 T  `) z/ R
   bmpWidth=bmp.bmWidth;
. J$ b0 a; K& w. N3 ^: m% J" u9 ~7 c) `9 z
   bmpHeight=bmp.bmHeight;
: Y, D8 O+ S8 Q: A8 D9 A+ m! Y) r/ L) R( d  j! U% g
  dc=this->GetDC();% E7 U$ G5 k/ q0 V: T( O4 q

- R* B) p- J+ G- Q' g+ H+ E4 h   dcForCross.CreateCompatibleDC(dc);& ~% |! \- r$ F# D3 t- c
- d3 j6 M6 H. n# _, Q8 s
   dcForLantern.CreateCompatibleDC(dc);
) m; H! _. o/ K& V0 W$ O% J
& |7 B( E( i+ ~  `   //将位图装入设备环境句柄. n. Y1 r: y6 W) e, ~

/ K0 d' j5 N4 b$ N5 A   dcForCross.SelectObject(&&cross);
8 {* j- F* a- p# ?* ~+ y
5 O' |' T' ^  ?$ B0 E  `   dcForLantern.SelectObject(&&lantern);
* H6 q! A+ j7 }  D* y% }& B  ]- Q/ y: E9 h" V( P" a
   //打开计时器
' L6 {! l7 G- B# M: d6 ]* W$ G
. F$ P. q& l; _+ m8 {! B   SetTimer(1000,50,NULL);5 F# F4 S  W$ f. w$ R' U
$ G% O' N1 ]0 A8 F* D" j
   return TRUE;
' R( N# m7 n: G6 N& ^8 R7 f& I1 x$ L( B" Q) ^3 p. o
  }
  y+ b1 S% W3 q( ]- F9 g! v
, }' C' d, e% c' _/ \  void CWipeImageDlg::OnTimer(UINT nIDEvent)/ `/ d. U8 R$ ^. L  T
6 {+ c" c5 k. p% ^
  {
/ R" A% z, ]) O3 p* V( r) f, B- K4 \
   //图片透明度每次递增5点5 B% b3 B7 L# r( V0 I
# F! g7 Q8 I( X' B: G. U! D+ u
   m_bf.SourceConstantAlpha+=5;1 h: V) U2 x* |  |- x" o& @

( q  S, D5 _! ?- l   //当第一幅图片完全可见之后,显示另一张图片
$ @) v7 I, j+ ~0 i5 C6 F- h0 r3 Z7 N( b8 E9 Q2 q4 s
   if(m_bf.SourceConstantAlpha>=200); o' X1 t( n/ k: d' r/ \3 V3 h2 r+ q

, [6 f$ A" d" R   {
5 U9 H  N  K9 o2 |6 N( q
4 I. ~3 ]" t+ l( z' C, ~$ L& T   m_bf.SourceConstantAlpha=10;
* Z( N4 r2 g, b
( b6 [. W  K; p! w   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
% N" {: {. ~6 _9 j) _1 \" S2 [4 l+ H9 b
   bShowLantern=!bShowLantern;
# ~9 U8 ~  e- [: i3 {
; G4 D. J4 Y5 s% C6 k+ O6 ?   }$ S. B8 {& ~/ C, @( K, {8 ^
. |: @2 O4 K. l  I
   if(bShowLantern)
! E2 l3 y" M8 c+ ^2 ]* o5 _5 i, q+ r6 S# v6 ]9 `9 x) G
   {
- k7 r1 {. k; n2 r) s* [: O# D+ S  A5 A1 n0 e, |1 H
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
8 D1 b" y; I) e3 [. x! v- K' `% ]. G, t) B( O% e! J. h
   }
% Y% ?7 U+ L( v( ~2 i4 Z' K+ y: B* j6 M) q
   else
: ?5 z. \( H0 z; @% P; L+ C3 [$ Y3 d& q% p3 X8 f3 _& n. S# ~4 D2 R0 Z( ]
   {
: I/ F* Y8 d" }
( ^, Q6 `  ]7 F% ]( D% t   //按透明度递增的方式显示“背景”图片9 |* r. f# b; t1 Y7 n% I

; B) V8 _. u" t) a   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
9 U( Q, {! j7 n8 N8 f( T; v3 m4 A+ t# b/ i5 w% @# n
   }
: H) ]* M4 N3 t- \  s2 A- {) P2 `, f: H' B/ H
   CDialog::OnTimer(nIDEvent);4 m' x' t' [. b3 F3 X# H; t6 F% E* Y

; v8 G$ p/ j; q5 F  }
6 Y. G2 w9 a/ @6 M, O  |0 H& v/ Z( I: w! U0 n

" k7 E7 L) [# T- i, ^  编译说明
0 V: q" n9 u2 c, y. v8 M. ?3 e0 \2 u. c

! C& f5 p6 k. J6 j3 X  S7 E  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 09:18 , Processed in 0.018530 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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