找回密码
 注册
查看: 4879|回复: 0

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬 6 ^/ e( i& z3 N/ M- n

' u" D$ [9 H. k  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
6 d5 c. s0 T2 M  \5 U- K
+ ?5 q% I7 E% s
' d8 ^; P3 ^+ t% x- I  解决方案1 F" i; Y' E3 H  Z
) y4 R3 Q# Y' X, B& S8 V8 G

, v$ I1 x' K+ w0 p9 O  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:, i: ~+ o1 F% B3 e3 u

0 i- g8 r2 B: x7 W. E+ r6 x  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red 8 n$ g" Y" R; t

0 _4 T& r( U) c: |6 d& {5 z  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green   U. l* _1 p. u4 \8 ~2 G6 V

: r  l+ |% l1 t: c  [  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue# M* k$ y& l$ M' ?8 |( K
4 }* M9 B; @* o
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' ?7 |4 e9 R8 ^! Q1 I9 _: }! l  p' s0 J
( t$ w6 n: _! ]# G7 G/ A1 O: I# K  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
# X: D9 l( {6 u' |5 t$ |
5 r( I8 u; ]( `$ e% A. r  BOOL AlphaBlend(
/ D* B* X9 f% G6 G
  z* ?* a$ g9 K5 a8 _, X! W) d- A   HDC hdcDest, + D- G/ p) z% G' Q
, V" s0 o0 n/ q& f
  // 目标设备环境句柄
$ }9 z, L! n2 v' N7 u  ~3 y, n, s* c, k' E/ R( K+ X
   int nXOriginDest,
5 W: x* f: I" Y# e# t7 P
6 x5 X. {( N5 |3 K* ]* g5 ~  // 目标坐标x
3 z4 u" R; C( P+ K6 u% b9 l4 }0 Y/ k/ R9 S7 K( b3 U  j% {& ^
   int nYOriginDest, 3 [. U4 T5 p. L( J. g8 J* U

, @+ L4 q% I! U6 T) L. o" s  // 目标坐标y
  S4 r! `) t! U( v8 o2 {& {, ]0 o; M* C7 l/ P, f- l! O
   int nWidthDest,
" ]& _3 e! M; N4 F
" {" E7 z  R2 G  [% D  // 目标宽度
$ A. h( n9 V9 Z! t) L& }' A0 ?% c& L. i1 O
   int nHeightDest,
& K; k8 u* |, a  H* g. b9 q3 T$ ~$ [
  //目标高度
- z6 P+ _: N. a" ?7 e# B
2 s/ K3 S; U+ h# k% c+ [   HDC hdcSrc, ) U! b# g3 n! H- R
: l' \+ C* N9 z6 l1 `1 z3 m
  //源设备环境句柄9 ]' _# Y$ i+ {2 W2 O$ {
5 W+ B5 S6 z8 x0 G* f9 K5 j4 |' d! S
   int nXOriginSrc,
* U# e3 n0 _. l* w3 r$ e( \3 X, i; i7 f$ Q0 J% e% f4 o. j
  // 源坐标x
- H1 ]& @$ K: H4 p( g. g( u5 @* [0 F- f3 w  w7 r( l
   int nYOriginSrc, 4 p$ g4 p8 o4 L( e  X) |3 C
9 j. x5 A- K& Z( w; E
  // 源坐标y- m& z& S! Y. A; X, ^$ f
* m; f3 D2 U+ M, g+ m. Z
   int nWidthSrc, + \! z. l. U8 f. w& m9 j' K
1 V6 X: A  S9 x) r
  //源宽度7 n; E5 d& \8 O0 M# R

5 @. a3 K: A7 e( z- K2 G  L   int nHeightSrc, * b/ {1 Z9 s0 k. v  D" b, D' M

7 ?3 Q% b6 T) |  //源高度- E- d7 Q, W- @" u$ t

+ L9 y% L9 P+ O6 p1 A% I3 X   BLENDFUNCTION blendFunction   `+ t# n( X1 i1 t; t/ x% |
0 g  k: {$ S" h  b7 V6 ]( V; C
  // 合成方式具体数据结构
0 ?/ n  R" r; g0 \  }! Q3 B* r9 e' z$ E( D
  );1 Y/ X1 U3 [4 i: d0 ]: ]
8 m$ X2 T2 L' P* V* S/ t
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:2 D0 J9 A7 R  C0 x

3 t  c8 F: h6 o: S0 g' x  typedef struct _BLENDFUNCTION {
. @3 [; q6 Z/ D( B$ i
7 w% f' x: x+ c/ `   BYTE BlendOp;
8 M' e6 n0 V0 N; d" A
0 f( X" ^+ ^( n4 ~+ U2 [( j6 r   BYTE BlendFlags;//必须为零
" d" n) ^) [4 J2 a3 J% ]+ I# x
& b( A6 G) b( W) x: J2 X   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示0 n4 l, g" Z1 R. p) k8 w/ v
+ k5 v2 ?8 f3 ?4 c, D# m$ _
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA3 ^9 L3 r: F8 k$ E1 Z! K3 {
4 M3 l% S- }3 r5 e( y1 r
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
  x7 O; Y: M5 P; z4 R( H: S( F1 E2 ]. S+ c# f& E
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
- S  Y/ G- E; R8 [8 |; ^/ Y* z0 T& B/ K% A+ r

! O  B  R; i' C9 N$ \) [6 s  编程实现4 r' s8 E# b; O% W
  w* M9 C, |9 d7 H1 [0 d

  z0 S" |# V6 |% U8 {  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
# x% _' S/ @5 m3 h) C! ?1 s" d& j5 Y" v$ A
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
/ [2 I; l6 L/ n
( s3 R$ ^4 O$ T& x+ t' M% i7 p  class CWipeImageDlg : public CDialog& h* m! r% u3 X
  F4 ~: h3 s7 e% p5 c0 V$ V7 D
  {
: `" A' B& B* p& f; o+ u1 F8 @" D' j1 m. N+ ]* ~9 P$ c% t
  // Construction1 W# M* F8 V9 ~' @2 {
. V( P# D4 l6 ?" J1 Q  U
  public:9 t; o* s- a) i

  b- y, L9 u- J( t3 c   BLENDFUNCTION m_bf;4 V. R/ I& y. w7 t4 V& X

! {! U: U( O: k% p+ T   CBitmap cross,lantern;" \2 Y( W' D3 A" K$ ^% |

+ N% g7 G; r- r( U3 p3 c   BITMAP bmp;" O# ^/ Y6 D' u5 I" y! k5 \( m

( ^1 p0 O! U  Y% ?% x& B   int bmpWidth,bmpHeight;& a  W1 t( |4 N! _: x% w9 u

& b' g0 V6 p" @7 N5 ^4 Y% Q$ _' f   CDC dcForCross,dcForLantern;
1 V& L/ v1 c4 q& w) a- D2 ~
( I/ O4 i& p  p, K# O. j/ o   CDC? dc;. z8 b( A) U0 m: f7 }
' c9 N0 D; R( }. ^2 ~
   BOOL bShowLantern;8 U0 d+ a+ P* G1 h% Q3 F# ~
% N, }$ m2 p  ~* M7 @% x5 `
  ………(系统自动生成部分)
3 J7 n" k5 e7 |$ p& X, v
( s6 p& i# a- g4 C  }; 
- l* g$ @( `$ x% |' }- S5 Q1 a, `; n1 X$ j+ n' [
  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:+ q, T, U- q4 O& E- q

3 S* `6 H* d: w* P$ h" k  BOOL CWipeImageDlg::OnInitDialog()8 ^+ ]& ^3 g  T0 O
# B- \( C( ]1 g/ N
  {& s+ t, y: M% u5 ?; U9 K
" f8 M$ x- h' K* ?2 O' ^) I
  ………(系统自动生成部分), T9 Y. B* ~4 r- i% U

$ u4 o1 [6 V6 L8 H   // TODO: Add extra initialization here
) [6 r* u+ s- Z; Z: M; |3 ^
7 h+ L8 a; e" ~6 \* n   //初始化全局成员变量
' u6 g( |- ?' H7 l* u8 _8 V1 c
- q; h8 G: |- V! r7 ~+ J/ d   this->bShowLantern=TRUE;( V" o0 r" `0 v7 b8 O2 ]' a3 ]# v

  u  g0 T% P  ]7 e: y; ]   m_bf.BlendOp = AC_SRC_OVER;
8 o4 i0 e; L3 _' j0 `
& f8 ~4 N0 q  y5 T. O   m_bf.BlendFlags = 0;6 U: h8 n, M# ?' n  U/ v9 ]$ ]
- I. S/ j: e# M3 s5 G
   m_bf.SourceConstantAlpha =10;
# c+ t6 k6 {0 _
* b0 A1 d9 q* @- Z2 a* T( r4 |' h3 D7 _   m_bf.AlphaFormat = 0;
, d5 d1 I& O& e+ K! T+ p
4 M. w/ A! l) Y( `# W; |4 F0 q   //为节约篇幅,以下代码中略去对操作不成功的处理代码
# b' c, P- J8 m' X. B- W6 J2 v" j
  J% O2 T, z% ?2 q3 x   if(!cross.LoadBitmap(IDB_CROSS))) b! _2 q2 [- W3 V- N# o+ s
7 u" j1 @+ r  C* h$ @8 `( _4 A- t/ F
   {
4 {8 {/ R/ s( ^* Q  F9 l
3 g5 d" o9 J/ s! h- \% b   AfxMessageBox("装载位图出错!");8 S. T- B; p* a- I) Y* W

. J( d- e$ }& N+ H4 l* |% v( @% M   return FALSE;8 F* q; ?8 c4 r
4 [+ N$ b+ t* P+ Y
   }
( ]4 L4 `5 k1 ^0 {4 r' n* ~! I* Z3 f& K' c9 S9 `
   cross.GetBitmap(&&bmp);
8 b% a& u7 \4 J" S) k& \
+ U: w  k1 T  h; X   lantern.LoadBitmap(IDB_LANTERN);
  e! u& g7 K+ \& }8 k1 ~
0 q0 H2 ~6 O7 M6 _* H1 ?   cross.GetBitmap(&&bmp);
: I2 v. c5 c% H; Y) }: B5 P' G
9 d* P" R/ G+ B7 O' J5 [) C- X   //获得位图的大小信息
4 y' ^7 @& y& J0 R: o4 Z8 C" z& O$ D) Q3 M  ]5 l6 Z, Q6 D
   bmpWidth=bmp.bmWidth;9 W0 x. O) }9 T
4 d2 D) R* }( }$ ^# C/ l
   bmpHeight=bmp.bmHeight;
5 i8 p- a* F0 M  \$ h/ k$ I, [* S4 b
  dc=this->GetDC();
4 l5 U( o( |. Z4 b) w7 U) r- |0 \3 W4 C
   dcForCross.CreateCompatibleDC(dc);7 o7 b& H+ R3 A4 G1 e" b$ w

+ H9 G: t8 A0 u2 l: E& Z* p   dcForLantern.CreateCompatibleDC(dc);
9 y/ s9 ?7 Y/ X' |& n1 W8 |, S1 p& ]% b# C0 {
   //将位图装入设备环境句柄' E9 P* a, Q( B8 w9 W! ?* i

7 w* T0 P) h3 `+ O   dcForCross.SelectObject(&&cross);
! }3 q3 Q  u6 A* V! N/ w2 [3 ]0 K: S, j% x: N$ k! r6 E* x1 A
   dcForLantern.SelectObject(&&lantern);) f" G8 X3 ^' [! M

; G' C8 c9 n, T' x- a. ?/ P) `   //打开计时器
& Z0 M7 C" h+ i) s( m/ h
. c, S3 V. O5 F: w  ?5 T9 n9 g* K  t   SetTimer(1000,50,NULL);
3 o0 R7 i2 _. {7 o
( k& J/ R7 D9 C  m9 [: I   return TRUE; 1 L2 A  e7 t5 ~3 w- f
0 I4 I6 A& Q" j
  }
& R' Y! @8 [; ^, X/ @2 w. a% ?- F1 l5 V- P4 q( k$ J
  void CWipeImageDlg::OnTimer(UINT nIDEvent)
- R( C' t  Z$ l" N8 k7 K) Q. r: E, i* {. D& A, n
  {: I* \! [0 t; x7 [0 g* P1 \8 O# v
8 d0 v/ g7 b5 s8 I: w
   //图片透明度每次递增5点' I- {7 k- m+ Y7 y
3 a2 E$ q& \; t' g7 m
   m_bf.SourceConstantAlpha+=5;( p, B1 t1 Y% x; M: K! [3 Y9 w; E7 d" I

$ P+ U# g" F0 }: I% E0 k   //当第一幅图片完全可见之后,显示另一张图片3 |0 R+ M) E) p* T  m: v
2 ^% ?/ D+ S/ E& ~. i
   if(m_bf.SourceConstantAlpha>=200)0 ]% I) T5 g4 p. C" k

# F% V' W$ X" q% P5 e   {
, A% l+ T. A5 u- c/ U8 A
9 q2 e/ {* H4 z+ a" h' ?7 r   m_bf.SourceConstantAlpha=10;
% }: v* Q2 z/ c+ S* l$ H7 p* i  P5 G/ S
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
0 H0 x; j- M, x$ K' w2 [- q) i$ {2 M1 D% A; y
   bShowLantern=!bShowLantern;
- s+ g! T- X. ?/ M, W+ _+ ^
; [8 B  n% c; ?$ [; t5 A   }
3 U6 X, I& w1 \- X" H; B4 L# P: [0 n! S* [' ?& i3 _) [  e/ G' H! U& Z
   if(bShowLantern)8 b; v9 y) q9 r6 p. D

0 T" F. n9 }& h' A   {$ `- k* S, ~$ z* P7 }- T+ v$ @
$ w- [) c6 y9 b5 `6 S$ O( X3 `
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
( y: u# r' Q. _5 N5 N" }
4 F- s# X2 F7 ^3 ]% H- ]   }
" I: M% \$ {1 |9 C+ Z7 r" U& }+ |/ q& z) d" j4 ^3 n8 [2 r0 j* w
   else8 ^" e$ \: R8 B% z; u1 x0 u

+ L! [7 X0 a; J5 r: `   {9 P& P# Q' Z, T' ^, _. u+ d! P7 `- ~8 M
) J( N( }. i. e5 ^+ m: a* O
   //按透明度递增的方式显示“背景”图片( u5 G& l9 p/ M* K8 P

. K8 p0 u; l- U" H; g$ ?   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
: U+ {9 J7 V% U! h: Q
" _  |# u9 R1 G+ e   }
/ ^/ m" [4 o/ e5 U& v2 x
  J/ R" g1 M2 N   CDialog::OnTimer(nIDEvent);
2 g3 w2 H0 [5 Y4 k3 W0 h; F  l
" J# {/ @% r7 U; ?, \  }; ~1 o1 p! z8 P- j8 Y1 ~

; y$ B) o% e+ y7 Q  C
+ e: o, d7 i2 ?" N  编译说明
# T) f8 D+ {9 r- C( _9 e& F; j2 ~/ b3 a: w( E
2 f+ f0 `/ c. x' z" n( s3 ?
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-10-2 02:39 , Processed in 0.018961 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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