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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
* T. O" @) @8 m6 e" j8 w4 E) W# ]% i8 [  Z( r+ G
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
$ k& P4 b  j4 O% P5 O) N
. O' w1 n7 \1 C; m
  w7 s8 {1 ^7 k7 D1 I  解决方案% o7 |! [' M  w5 h: y/ a8 ]
  i7 [; b8 I! A: n) B  z4 b7 b4 D0 g
- k+ Z8 z; W7 o2 H5 L6 X: H6 D7 N
  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:6 i* ?  ~7 e8 x: D/ l/ f' O4 F
" G  K8 Q$ t( G5 q$ f
  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red & `% x& V" C: _8 A  @2 s' O

7 t# }1 g+ [( @' C% ]8 f3 P  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
, @/ C0 M5 E, E& t6 B- N5 \/ \, U. ?; `- n8 @7 i3 H& L, P9 h
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue6 T* P5 b& ~" d7 n

# ~2 |9 i/ P! w0 r( s1 C) j  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。* O; G, E4 I$ o" b* v. l( t
, K: F; X: ~/ N8 j+ B* s
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:6 T1 v3 b" q0 b- O

# V  D1 ]7 E2 B- `$ l, U  BOOL AlphaBlend(4 V3 s/ y4 V' k, p. N# X' g8 b
- _- t% S, V+ Y$ |# R0 s' s4 x
   HDC hdcDest,
9 T- o- v+ |( R& ~6 ?0 c* h' [2 e. c( J$ p3 L" y
  // 目标设备环境句柄
) y' L! D0 [0 O" x- d: ^' q- o8 N3 P, K+ A
   int nXOriginDest, ( h1 b, Y5 t2 {, m
: B! x* P# A7 M6 ]: Z. L/ u
  // 目标坐标x  M: g) A9 i8 R$ `& }0 n! V

' }. R) h0 v% Y; D; o5 m! m2 q) ]   int nYOriginDest, . g  L$ V0 i5 Q  C( j7 {
+ }9 H* E$ E7 u% d9 J& M
  // 目标坐标y
0 c8 j+ g8 Y! F  l+ i% f
8 D5 O% s0 y( p6 b$ j   int nWidthDest, 1 u8 E. Z. R: `
1 x: U/ p1 O0 s% i6 ~/ K- }6 X
  // 目标宽度
9 n+ `% C/ l, I' m* G2 A8 J& s8 k- _) X7 E$ a" B( r
   int nHeightDest,
, r2 U, g& |: p- W" K$ e) z% w% n' f- S2 _( q8 k  q, [( C
  //目标高度; C; S' n! l; Q1 s; l5 }) Q4 ^
3 b, w2 c& k* _! v
   HDC hdcSrc, 5 g, S+ f, u2 J1 B7 O& G

9 l$ T+ e: h# r4 l; r. S  //源设备环境句柄
- w5 T8 u; s. S! f4 Z: ]
' ?+ p) ^  o! a. z   int nXOriginSrc,
3 A. g# F' J# [" g. o' |$ g! d9 X4 z4 \" C: ?2 o0 |
  // 源坐标x2 o7 c/ ^5 p( c5 s0 a% d; C

/ Z* F. x$ y( P/ J! [& Y   int nYOriginSrc,
/ f- `4 f1 a# G2 I6 i
0 ~  }" {) d) A9 L+ R! N  // 源坐标y
! x% m5 s' h- P' j. f6 b
  @6 G# r! }) k8 J- I( [) Y* ~   int nWidthSrc,
7 v. w' ~! j; h0 }, H: u& l3 W0 F2 v2 y' q
  //源宽度0 r; z0 [- v0 Z0 q3 E' e$ v( o) ^
4 \3 E% O8 R6 }
   int nHeightSrc,
0 u, m( Q; `. p1 {, g7 }
* i0 p* Y3 @9 g4 O' o4 \; v- R  //源高度
" o! V7 R, E: K& |* u. P8 |- l# r  U- v6 C) p8 K" C
   BLENDFUNCTION blendFunction
9 [) L, F# D% m* \" K9 r
% ]& `1 p6 H* w, h$ D' k  i3 i  // 合成方式具体数据结构3 \0 `8 K" F/ K8 D
4 E9 U  ]7 k- M+ h$ K' Y' s7 L1 @
  );
, N9 u& j8 j% L1 o
+ x8 @* Y( a1 \7 k- e7 V& @& B' t3 l  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:* U. Q5 B4 Z% O* R) U) B) {5 z2 i. ^
1 f" x! U7 h/ J# F
  typedef struct _BLENDFUNCTION {2 X5 U+ c0 e2 U  c! x

! Q6 r+ c- Z# c. Z: k2 i% }   BYTE BlendOp;
1 e+ [. W3 B% G9 |! D0 ]- S+ E2 Q- o0 J/ h& V, M) k: }( T
   BYTE BlendFlags;//必须为零
2 z5 J. I! @% p' K, B# O' C2 f) T/ x" ?- h% u9 H9 T, d# W6 ^! c( S
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示7 V" m3 }7 @+ l5 X! B! V
: l  \5 E9 {; Y
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
/ R. l1 _& b) `/ _" Y: g; B
* X" Z" S* J- e9 R  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
% T( y! |0 `# {7 c2 A+ y. z' j* i+ f; S" Z
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。$ e; e' M9 Q% `4 g
# ?7 g/ j3 d' e" U
- \7 c. n) G4 v. g
  编程实现8 N. n* n6 Z' }) {) P! p0 ~# v" z/ r
" E; e+ m8 U1 G% C
0 t* C& I' s8 U6 g; I# M3 t$ u
  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。: Y3 c" l/ F: r% e/ g5 L) M2 m

" n; N( S( w: T- y( i  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:( Q: Z( o  J6 t6 J

4 k8 ?& [. A/ A5 t$ m6 n  class CWipeImageDlg : public CDialog3 X8 [2 H: ^8 Z6 H) @' h$ `
/ m) z2 |& x( \, d
  {; T0 P  t# ?* \1 x5 }
; v" X' B& u) ^: ]. [3 f
  // Construction1 s$ }* Q- s) Z$ k* a" n
5 u4 m, R: S! w' F* f0 u: E
  public:7 ^+ L4 A2 k2 I$ h" y2 I) m3 i8 C

% _$ @, l: n) X; I1 {6 M1 t1 F   BLENDFUNCTION m_bf;4 ?" f3 s, l2 H, H* q. j: x( c

( v- \, }. p3 w1 B1 t   CBitmap cross,lantern;% }1 h. [) I9 a3 _  P

* h8 d# S8 Y) D- `: _   BITMAP bmp;1 H* j2 g: A$ @
5 B6 V: `) \: ^5 i9 e" e( I* X
   int bmpWidth,bmpHeight;  v$ G. b0 Q# t* n. B+ s6 {

/ h; F. g* E0 C8 f* E& u+ @   CDC dcForCross,dcForLantern;
, u2 H$ \7 s2 x( `1 ]7 I: s( U' e+ K$ t
   CDC? dc;
3 J! O' p' {* j5 C3 U' b; s: S
! I# b* p! ]- r: H   BOOL bShowLantern;
3 z' Z- V+ Q  j: P0 a! [  U" D; x- Y0 F. ]* @
  ………(系统自动生成部分)
% o( W7 ~9 M6 u# k* n8 u
) B3 D8 K$ ^/ _9 k0 ?  }; 3 t" J$ B9 V7 }/ v

! R( Z0 G# ~# h- P$ G# ~+ J2 t3 Y  s  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:# `$ C$ Q7 Y: K  N8 b

3 k' `# s6 X& h1 I* R  BOOL CWipeImageDlg::OnInitDialog()  l0 q9 z8 R; c6 z

* Z# t' _$ u; `( `" K$ S0 C  {7 A* Z: @. J0 a

  |6 P: B6 b+ M2 h$ L! K5 l/ k$ d  ………(系统自动生成部分)' S1 H$ E5 j) ?
( p& I: @3 \+ X! k
   // TODO: Add extra initialization here
3 {8 e1 x4 r4 V; {3 G2 O3 m" x7 A, w# F/ S9 C/ N7 e
   //初始化全局成员变量1 [6 Y1 D- B( |" b4 c
7 g& X" }" r; z! k% z2 T+ K9 r
   this->bShowLantern=TRUE;9 t1 ~$ g5 L1 J) c) S' x2 t
: e6 n3 ^! e6 s" q
   m_bf.BlendOp = AC_SRC_OVER;
- B# J/ g( d3 ^" U7 f
& A7 ?8 t  i! y. t2 _   m_bf.BlendFlags = 0;9 i9 t) J* A  y6 X4 c
, j" _( S8 p3 R6 c5 s6 _
   m_bf.SourceConstantAlpha =10;
4 C" x' @: o: u/ @" J5 x7 D
8 K) O$ z( [) T  a' z. G$ n   m_bf.AlphaFormat = 0;
7 v# q9 q/ G7 ~, s5 o# V7 U  G
1 S7 \" z; J8 E! J  P9 H   //为节约篇幅,以下代码中略去对操作不成功的处理代码7 _( Z. T- ?$ ^- ?. I# g3 C
# I4 ]1 ^9 N4 m% T# }" p
   if(!cross.LoadBitmap(IDB_CROSS))9 M9 v7 ]: t& |
" O" }' }$ t  X* |
   {
; z9 h) {3 ~- P$ ^9 S* c! B! e* s4 l6 |3 {1 W) u, _
   AfxMessageBox("装载位图出错!");
7 b" Q/ z7 n9 X0 M& l$ M2 K& I: ]$ Q/ K) R% o
   return FALSE;% O8 P+ v0 `6 B+ J

: }6 Z6 c# p% [' ?- V$ `, S5 N8 k   }* l$ _1 u& u5 ?' ]* A; X  F

9 x' Z7 A. e+ R& l* a   cross.GetBitmap(&&bmp);
3 x; b6 S) f; V7 n) C# j
  h6 H1 t9 c3 D9 [" ]   lantern.LoadBitmap(IDB_LANTERN);: j) B( S1 h/ z$ c! d9 q. {0 j

* L$ y2 F2 U/ T! s+ q   cross.GetBitmap(&&bmp);0 |( x, H- X; X' B) L! \2 D
- z% f! @) l$ a
   //获得位图的大小信息
3 P, i3 E- ]% r$ c3 W* v  {% s* d0 u0 u8 |( F8 H( {  ^. H. A
   bmpWidth=bmp.bmWidth;
3 E* Z# @6 y! \7 D+ |! |$ V3 V+ E/ f% {! }- L
   bmpHeight=bmp.bmHeight;
: u* E) K. r4 t. H  a3 M* {# f  ?+ S- b# Z- [( V
  dc=this->GetDC();
/ v  e# [( `: e' o/ c+ T# v0 t% {" n- d8 D5 u0 j
   dcForCross.CreateCompatibleDC(dc);9 h( t- t+ ]7 W' m
7 Y8 H8 y& J' L+ s6 A+ J2 `
   dcForLantern.CreateCompatibleDC(dc);
# a/ M. f/ v$ N+ q+ C1 D5 p  Y& I, f( u1 h  U, n
   //将位图装入设备环境句柄, q% u7 C& z: y; p
, B( p( w0 u5 v5 L
   dcForCross.SelectObject(&&cross);9 x% G1 y7 R' G2 y/ B  s

+ Y# L$ }0 L; h( W1 s% d   dcForLantern.SelectObject(&&lantern);% u; V2 ]7 C0 M- l9 P$ j  q/ ?

) @0 L! E2 E& p7 F8 G( B   //打开计时器
/ u$ E8 m+ @8 M6 B9 W; s! z; X3 }9 @/ p4 P" H* `. @
   SetTimer(1000,50,NULL);/ Y: Q4 O9 {) A7 l: M# g( m

! z4 ]/ P& o& h! o' x7 g   return TRUE; 4 D$ J( J$ _. g. {& o- \. K

; l! X1 S, [% L! p  }
/ d/ J1 j% {" Y4 A# `. `& Z# O1 d8 i5 ]' K2 W' O
  void CWipeImageDlg::OnTimer(UINT nIDEvent)
% g0 i* k" H5 ?" ?! g+ I9 X6 m2 T& w& y$ u# O% u' e
  {
/ F  R4 \5 \& h+ k; O9 H3 i1 N' D4 y! u, S- S6 k% {
   //图片透明度每次递增5点
* p) s+ K4 z  ?# G$ [  f, h# `0 q/ t/ v& \0 ?, k3 n0 w
   m_bf.SourceConstantAlpha+=5;3 w, A9 w, @% ~! W& y( Z0 V( `! Y/ O; E
! u5 o- ]( u! l. }/ P' I$ N
   //当第一幅图片完全可见之后,显示另一张图片' C: E! d1 v. W( \
4 @1 C7 Z/ R. Q% b9 F$ N+ D' _1 K# L
   if(m_bf.SourceConstantAlpha>=200)
2 R& A4 J' U+ j% u
2 _; {: R, u; K2 e8 Z: F2 n   {4 w. f! s$ |) O9 @9 o
' T& [' b8 _. h4 D. s! i1 M5 U$ J
   m_bf.SourceConstantAlpha=10;9 d0 q, U& Y# f! d8 t0 m  r

. i! c* @* Y$ v  c$ N* J   //将bShowLantern做为显示标志,确认应该显示哪一张图片 1 x# m. H2 Z# C7 y

, @1 S- ^7 Z# \& ~9 ?   bShowLantern=!bShowLantern;/ M: `1 ?2 _  C4 k" |

1 b+ Z$ q) G& c) I0 h   }
5 c  ~, O( p' M$ j9 Z6 B9 j5 ~/ r4 L
   if(bShowLantern)
* A$ {* U2 D7 v/ b5 m7 O+ a7 m$ }* X% p6 v
   {/ I* F4 s. }, L, n& O
6 e" s/ X  S2 ~- E5 B7 V! R. Y
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
' I6 A2 L0 R3 B: A. I: S) J- O! G9 x% m. D
   }
; i7 y" {$ f2 Q$ I- F9 v0 W6 v
" m2 o" w! `. C% r! D% b9 N   else  c4 i9 `* {, ]1 [
+ @+ X; k* S5 H' w% ~, p
   {* \2 c3 Z; g; D$ a

, w( g2 _$ f  p& P7 ]   //按透明度递增的方式显示“背景”图片( W/ c3 q" {& v. P4 _

* h2 a: ?; T2 h) y! U$ d3 l   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);* V7 u1 E& c- Y
- \6 F: n( p* F2 _8 P$ Z
   }
: R  Q6 K( _( i4 K1 B, r) \7 _' L$ c" H5 q. R, w$ }
   CDialog::OnTimer(nIDEvent);+ i5 p( `" h3 N- r: _$ a/ v- k
' Z- t  t  k! v& o
  }
! N0 Q7 t% V& m2 @# }9 H
5 b* z8 R' j( h. W/ s0 F/ B: ^/ z- n7 ]9 y% B% `
  编译说明* k; a! f9 N& K9 X6 }, x1 _0 _

& Z; C0 R5 d: _( K# x7 E) z4 i1 R1 }: T7 p
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-6-18 11:30 , Processed in 0.020237 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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