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

图形擦除技术及编程应用

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

2 K8 y2 [* i+ r8 u0 _! E  解决方案' h8 x4 h6 ?5 T- E3 }7 M) N
# f% {! _3 U$ S( R5 c

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

& `4 v4 W1 F6 m8 X) p  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red 8 l7 [! f9 G0 |" `9 }- R

* u' [& Y" V) p  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green * |9 R+ L# L. ^4 ?

, q) y0 k& ]+ {% K  c  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
4 z5 g& K8 M# @8 s  H' q
- k9 J& h, Z0 K2 U5 i: d# y: ]  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
7 D. n. C$ ]0 d
0 u" [" M% y7 Y' r6 _' G  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:  @5 U" L9 F! S! |0 V* W
) @" ~; d- K! j3 ~% a$ z
  BOOL AlphaBlend(; O& b  K' Z; W

3 d5 n0 m$ ]7 _2 k4 e* Y* Y# D' Q  G   HDC hdcDest,
# ]8 l7 d- C- K( ~, F" O  C* X! E8 e" u, t8 s- g9 I
  // 目标设备环境句柄; r' d) ?& V/ `& P: a. D
! \4 K- x5 J3 o( N
   int nXOriginDest,
* c4 J$ w* |; p5 j" c+ h, A$ Q& M. [* D; z- k0 Z3 C
  // 目标坐标x4 h3 y' r  s5 r0 R$ `% e5 H
( `* t/ E5 X5 Y+ R, L0 n. S
   int nYOriginDest, 8 i0 d* K7 V4 s" @
8 z0 r, L0 {9 T/ o' a3 s
  // 目标坐标y
- H* ]& ?% i9 n, {) m' ]* e6 q$ f' G$ O4 S7 q: \
   int nWidthDest,
+ f5 n7 Z0 S7 k4 I( F8 c! ]' Y: D+ a7 e8 |
  // 目标宽度" g- h9 {! P2 h  ~5 w( S+ ?3 |
7 g) S1 Q9 A1 v2 w! }+ F6 N' x" C
   int nHeightDest, % T2 D! p+ x! m! e
. }+ U* l$ |$ v/ z) x
  //目标高度+ w8 \8 u) K7 g( M

# K+ d% w2 r5 p4 w6 w   HDC hdcSrc, / s. A# O5 ^5 K9 W- ~6 q; q

. w' v, D- K# |) i' S  //源设备环境句柄6 l9 \& R; P0 M

$ h) [7 P% W0 ~, O3 r1 R   int nXOriginSrc, 2 N/ X7 q& k% V9 q3 {3 N/ ^

# `! C# V: _. R+ O0 Z  // 源坐标x
% Q9 b7 u0 S1 ^' L5 @. A5 N# F% ~% S. i3 t$ T: @. b
   int nYOriginSrc, 2 Z2 T5 n2 R, C5 V
! S, ?$ p" Q/ _# X- s
  // 源坐标y
/ B) v  [0 Q+ V# u/ k
  `- \9 Q% W% D+ F   int nWidthSrc,
* b) J5 ?$ r- M. ?& L/ |% {0 A: p3 I' Q% H/ C/ `
  //源宽度5 |8 F6 y, h- V5 @# |
+ a, R# q, U# J# ^. q
   int nHeightSrc, 4 u( u; E8 d* k9 H. E: U. k
' j6 {4 \) {7 E8 Z/ P6 O0 L
  //源高度
- A: V6 o1 T: C; Z! @6 c! }
1 O' `; `; ]+ `/ Y9 N   BLENDFUNCTION blendFunction
" G. O# B- d& `# ]% T
; x$ x# y% }% m  // 合成方式具体数据结构
0 ^: b0 \) a/ g6 n
: D6 E: L# _" e6 z  );5 X6 C4 ]2 u+ T: @- l" K
" V6 c4 Y" F( `
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:+ D) \1 f' l" C! n- T7 m

9 q1 Z6 L; c0 L/ u# U  typedef struct _BLENDFUNCTION {, H. W- E0 ]# v- \

1 c, a  N' Z; o/ }* s   BYTE BlendOp;4 X* v* b0 I1 q. C

6 Z1 g/ s' u  o& n6 l   BYTE BlendFlags;//必须为零
+ y0 r! J3 z  h% S! A/ \. y8 z- e& ^$ C, g2 p4 b
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示- ]/ c: M2 o) M& m( N) n
9 c- \; K7 p, {/ o# _3 O& h
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
. X$ N* z* {$ ]& @" p$ l$ `  V' f. e( s7 {
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
, M3 {1 o' E1 ~& |( s- d, ~& G: S7 B; }/ p0 U! s6 ~
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。8 g/ C1 b* `" n  O2 I
5 {9 F: Q+ I2 E( n
0 @: q9 U9 A( x3 s$ q5 B$ K: k
  编程实现
$ o, w1 D4 S3 e/ O
2 k. q9 a2 u8 \
$ H! {' F9 b/ `  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。2 x- P$ \2 D9 z- z4 q
$ v, k) H, m& V6 |% Q
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
7 `( X0 e! D9 A1 G& }0 Y' e- W
, V8 v- }7 U  n, \8 }7 l  class CWipeImageDlg : public CDialog1 S: U2 B2 K- |+ d

0 G- Z2 ?+ r. u  n' d8 D4 ]9 E7 E  {9 O  j( {* H2 G

; i/ I+ g) Z7 ^  Z2 P3 w  // Construction1 M* L' [: T* f& B. H

$ Y9 z- x! u, c' s  public:' g0 D2 o/ Y7 h4 C

$ t2 U$ `  S) ?% [   BLENDFUNCTION m_bf;9 s$ U8 q5 }- t/ ^, N& l/ k, ^% v
, g9 M4 {9 U) Y+ a1 k7 @3 J
   CBitmap cross,lantern;6 O3 S3 ?1 I" F; V1 G3 V
4 [: b' o$ |" f$ C" `! k$ _
   BITMAP bmp;% m4 _( C8 H9 j' v
3 @- ~/ O0 |/ z3 ^. r6 ]
   int bmpWidth,bmpHeight;
( c6 @( J  G# w7 X% h! b3 ?" R& ^. z! q; h6 z& b
   CDC dcForCross,dcForLantern;
9 t/ r3 ]7 H$ H5 p8 T& D1 S0 M# K' D1 g* f$ V/ W& u! h: J
   CDC? dc;
) Q( f; M% D* I. m5 Y) v. _' s* D
   BOOL bShowLantern;8 l+ G* X6 B6 ~: `! p
! y. ~7 E3 w6 s( Y3 N$ ~
  ………(系统自动生成部分)
. U) X& P3 X' h7 D( j
- Q9 L/ F; Q3 N: m0 t  }; 
1 ?' X: g* d# B$ e9 a* W
( @3 {- D9 ?1 A  N. g5 U' D+ O  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
6 B" u& u- R+ F' I( h) D  d8 m8 M2 M7 L
  BOOL CWipeImageDlg::OnInitDialog()
0 }9 _) e3 K, _' z6 R' F. ?5 H% l2 s8 ~* r" M
  {  S, L+ ]7 X; _% p. m% r+ l

( n2 T3 j' N4 X# [/ }9 n" h, ~2 x9 o+ r  ………(系统自动生成部分)! c: L1 d* B5 q

4 O* b- L6 b6 T, m7 h   // TODO: Add extra initialization here
# A# q( x! A0 v9 P; p' f* _( H( {. o0 \
   //初始化全局成员变量; q" g! E1 b2 I" O$ z# V) m* @4 d; H

* M5 \% q$ q% u9 \9 R: e4 z   this->bShowLantern=TRUE;7 {9 S1 c0 k  g3 A, A2 S  x

+ L8 h6 f8 o3 D. x/ n   m_bf.BlendOp = AC_SRC_OVER;3 }$ k+ \' R6 ~  m. N8 m4 d* }: I1 E

- l% Y* O% i- P+ v: I9 Y4 Z- p   m_bf.BlendFlags = 0;
: `: @5 `$ I6 T8 x/ A4 f( Y5 n" Y/ K6 i% |. M6 o0 \
   m_bf.SourceConstantAlpha =10;" \5 A5 b0 v" `, ~0 b
$ d% }9 q9 K$ T* c
   m_bf.AlphaFormat = 0;
/ X/ j. V, D/ [' f7 c0 r: U, x; f6 ]! ~& n# f
   //为节约篇幅,以下代码中略去对操作不成功的处理代码0 i- m! t* ~+ ~0 k; w% i0 M
$ F$ T. Q5 b& A! r. x# R$ W' s
   if(!cross.LoadBitmap(IDB_CROSS))
" p. g2 i& A6 y. J
$ N) R- P! o# n. |, {- o. [   {
- k  S& m* b* K2 |
/ w' Y' |% M+ A7 Y   AfxMessageBox("装载位图出错!");
- ]) V; F1 U4 g/ `- B  [
- }, f& W+ ], D& D# [2 F   return FALSE;
; _' b6 X! z& E+ x, W7 o0 z
5 G+ T# G; c5 Q4 ~# R   }  n/ w+ W' {# b) [( ^0 j
  I* S$ ^& _; w# W7 o5 r+ D
   cross.GetBitmap(&&bmp);; d  A3 e0 X8 p$ M+ Y1 d
: ?1 H* K1 H5 @  J) N
   lantern.LoadBitmap(IDB_LANTERN);3 _! a9 k0 d1 x) d0 M  z

( @' b% {3 k5 ~* {: k   cross.GetBitmap(&&bmp);
3 c+ a6 m+ j( A4 U1 }
; Z/ D7 l5 w+ ]   //获得位图的大小信息- }% W6 m, ]( K- _2 b% @' W& Y
6 i7 f, V; G! _4 {: `* u; ^& F
   bmpWidth=bmp.bmWidth;
" l) t, e) u" h7 R" {  s7 U
0 C; T' t. h/ a- C# W5 d) b6 V2 [1 I   bmpHeight=bmp.bmHeight;
0 L0 C! u: k  [# B8 @% \6 N* k8 H* T9 Y! J5 |2 P1 D
  dc=this->GetDC();
# H8 ~/ x. ^: F0 d" _4 R/ z( _' R7 X+ b% {
   dcForCross.CreateCompatibleDC(dc);3 |" S; R" h5 ?; L2 J
) ]" x& v! i" s. |, Y1 u
   dcForLantern.CreateCompatibleDC(dc);7 l! \% i; m1 g! p; Q& r5 O
0 }7 S! v$ l; ?3 X' b8 O7 S& w
   //将位图装入设备环境句柄- a1 C7 R1 C2 c& \/ C5 x
% f" B1 [( [" I9 p, D
   dcForCross.SelectObject(&&cross);
. N' [' s# Q& g" T, H- K) |/ E9 O1 g+ N1 N
   dcForLantern.SelectObject(&&lantern);/ [, L9 X5 j" _) C: G! _* r# u

4 a1 T2 I2 c7 k$ S/ B- ]1 R   //打开计时器( Y& `. v5 k- l# L  [3 ~0 c

6 g1 A# B% l- c( |3 k4 L" P   SetTimer(1000,50,NULL);* {' Z, I# O% r  z  a

; E  {/ U- d3 {- \( E' I+ c   return TRUE;
" C  C) P) i+ J* H5 {# |( Y2 Y# }, E  A" L# i+ S
  }: [( Z: E0 j+ I6 ~  F1 N/ S- y5 G

* v1 `' g( E' Z8 @) O  void CWipeImageDlg::OnTimer(UINT nIDEvent)' z1 O4 ]3 L$ h' p2 H! b

! X( O& ]) F. |& a' F' y  {: @* z) j. t$ M' ]& H: l  i

/ t0 _; }: s# G- y0 v   //图片透明度每次递增5点
& T" z# J& ~, F
9 s* e6 V+ J- ?( w, D# E   m_bf.SourceConstantAlpha+=5;
- p7 y1 u0 {5 X9 K
! u" Z3 ?* P+ p) d   //当第一幅图片完全可见之后,显示另一张图片/ ~3 b6 Y0 z) E+ c2 l8 {0 P* X- ^

& w" q/ l- F* h$ g4 F; M9 Y" Q   if(m_bf.SourceConstantAlpha>=200)& r9 k' {$ e: H
5 J, w' F1 B6 p+ k- S2 H
   {) Q. K0 J! E2 s0 }, R8 n% C7 Y
! ~6 n8 ^. d; v7 {
   m_bf.SourceConstantAlpha=10;4 k  D$ j+ K. S
9 X- s4 m3 b1 ?  M: F
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 # X' ?/ W8 N5 K! C: E: D$ a. ?+ f2 E8 p
5 r  G$ s2 l( G. C# }: v
   bShowLantern=!bShowLantern;: e* r6 z/ z0 ~' u5 m) ]. {$ J

9 r4 \. H# s3 k+ @% x% I; h; Q   }- r0 I  \; J, X  M

0 g+ Q& c5 l. A   if(bShowLantern)
, _  c# z' [: j5 A" `8 r
  t4 T% X( y" M& U; l. @  p* \   {8 S4 w6 h$ o6 S$ q' \; \0 A1 m
& ^, h1 S# I6 ]. F% Y/ n! O- l) U
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);, Z/ G) K1 ?5 Q: Y/ y9 i. p3 a* I
; D# P: l) e9 ^4 p+ D3 ^. w
   }
2 u) T- M5 |, B+ r" q! p% l2 \
, q% t/ P6 p( u# y  I" ~9 A   else9 |! R  W2 [! m! [

% i& b5 o! U2 o6 K) J; u0 ~   {/ \9 u# L. ^& N- k

2 y2 R4 ]2 \, l/ }5 Y4 h   //按透明度递增的方式显示“背景”图片
1 p$ ^. f' k1 J0 b+ E: ?3 c
4 F* N2 o7 O- B& W% P, {   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);: g. w* C6 A, c1 J# M$ r. ~- {* k
7 k& Z! m+ G4 R/ `
   }
! f  E; X: n- [7 J- H/ ~1 n( n  u3 B# x! i1 z" s/ F% R3 H0 ]
   CDialog::OnTimer(nIDEvent);1 j& E- y0 B6 I# L# I- Y

; p  b9 g1 {. ]0 J, ~  }
% s2 E6 w0 r* Q  _4 T: d; R1 J
7 x- m* T6 e; [0 M: W0 t5 O* z- U" _  I: N
  编译说明% t. Q3 A9 E, T- A
9 J4 |0 R" @1 r; ^* i+ r

3 N1 Z1 j6 @  O+ i* ?  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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