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

图形擦除技术及编程应用

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

, l7 X' D! P( L& f% {2 b2 `/ W
! P9 ]; T$ l5 d; E4 M3 j  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:8 k) c& z% k7 D; V. [8 z

2 B/ b, i1 E$ B; a! t* d! R- p  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red " L8 v/ I9 u* ?+ e9 H! _5 q

* v5 y0 W0 x' ]$ Q0 j/ a1 q- g+ I  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green   b5 Y( @1 P1 o9 R6 A

3 H" A% R6 R3 b! F, D2 O  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
6 ]: C5 ]% E! q+ [+ D4 F8 `- Z, ?
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
' k7 X) K6 r" ~8 }/ K5 q7 n. t2 k; m0 n$ n/ Z4 p) L7 Z; |8 p8 ^
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:6 c4 C# ~# z5 P
$ L# M" d3 f/ m, Q* U  L4 d- p- ]
  BOOL AlphaBlend(
+ V$ u5 p! q! C8 M( f5 ~; d
" r/ e6 w) N3 y: ]6 T   HDC hdcDest,
. e( \4 z0 Z8 T) ~& ^) K7 @; \1 V3 u
  // 目标设备环境句柄
, a) B  R: x, t8 Z/ }: T- V' I0 f( c) p$ F
   int nXOriginDest,
4 P  B  Z: @$ @" p5 V* |2 U  _9 d0 ]; _& R) p
  // 目标坐标x
" k: I2 d3 }. V
1 W9 S) _# s4 V" G& _: `   int nYOriginDest, 8 F5 g/ E0 [  S8 s& h" F0 P
! l4 @9 `2 m; \
  // 目标坐标y
. r* O" X$ ?7 |* H6 ~: ]
9 R9 S8 U$ o- u7 s) Q1 |   int nWidthDest,
: R5 M! o. G5 @# Y2 e6 T5 G- h4 R) c  w  h6 u% j
  // 目标宽度
) X# s+ \; F; H+ h+ G) N+ T9 N, K/ j5 I, o. d0 _: \" Z
   int nHeightDest,
3 _7 c* l4 s; g$ C! N% z! D8 L( C5 f1 R4 A/ x9 L- v
  //目标高度  c' b# Z9 a9 U' {, Z

: J. [% @& A$ }4 H   HDC hdcSrc, 2 ^9 O) h0 z- i8 x
  J: M. E5 v. Q  \. j$ N
  //源设备环境句柄8 h" ]/ e1 U& Z0 c) z0 B& B

3 L& T$ o# g0 A$ T( E) x; {: M   int nXOriginSrc,
# b6 t1 r3 \6 [) m
  R7 S4 {; [! q# x' V  // 源坐标x
/ L+ r8 D; V: H' v7 r5 E
7 t$ W$ X9 Y1 ~, [0 m   int nYOriginSrc, * w( J, C' [1 k

  A* }% }- Z1 r; _9 Q: z  // 源坐标y
4 Q1 m) t& W: J! C8 a! J3 }
7 R: y. A' }' V, g" {5 h5 c2 _   int nWidthSrc, ) z5 [* @: {: @+ ?) V
6 v8 ~! P1 B* J
  //源宽度
9 @! P* b  [2 h, x' E3 l3 w& V: z: w/ S) q1 K
   int nHeightSrc, ( W- W' G2 Z- m  w8 Q1 N" D. i
) z" E. [, I6 S( X( i
  //源高度* z' k7 e/ ^! n/ o3 V

, W9 o7 m2 T9 A2 }# S   BLENDFUNCTION blendFunction 1 B* O4 a( `, F2 G+ l0 g* s
* A5 F; E; V9 ?; u
  // 合成方式具体数据结构' _$ h* u: F* }) {

2 k# o7 Z- J: Q3 x  );
2 V/ o) r5 ?. F: d  `, [) ~" g: R( O0 o( [# f% a
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
0 d& U8 N1 a5 r4 ~5 S8 L
; t$ [+ x1 \+ a6 u0 L. x- h. F  typedef struct _BLENDFUNCTION {) A. N; g6 ?0 X8 D  E0 |0 p

, _" B( v7 M5 p- d   BYTE BlendOp;
9 d0 ], ]% m9 x) W( S6 l% n2 c( g8 k) Q
   BYTE BlendFlags;//必须为零- y* `4 ^  `6 y
* w* S$ n; [3 I! h% e: g% g
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示. K" ^2 E2 q! m# h0 R6 j

" F& {7 E4 v# {" L, f; J4 z   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA+ k/ d! ~* z: z2 ], x
# k( j* P6 A' s8 \, t. E
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
, a( b1 a1 I8 J  J# ^, E# c, l7 Q" x; G5 j4 N2 m6 J8 ^
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。3 g' c) Q; l# P, k6 j1 m8 |' V

- p$ ]# ~) P' x$ o9 R
  j; t* U( ], C& R  编程实现/ s5 l& K* _( Q0 i" j
2 ~* }, {3 }0 p) U

3 B$ j' y6 V1 X1 P! q  I1 _- v  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
: O) M& }0 P" m$ z2 T1 C  W) s6 C' N# }) x. W* J
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:* C! d: A9 I" l7 e; e) O4 v3 Q4 ]
+ T1 U- Y, Y5 }  W7 x/ g9 w
  class CWipeImageDlg : public CDialog+ E. N1 k/ R6 _' _, o+ Y
, `) x- o0 J$ p! A
  {- e% `/ Q& L( l; Y0 F+ ]" O1 `

) t/ o9 l: \, L. f$ E; Y( [/ [  // Construction
: j9 }, s) t+ J* S$ h- a& c* D0 t0 E# |7 J% X1 c
  public:
2 Y4 e  W7 z4 t9 w
6 K( p  p9 q9 J" I0 n5 F4 {* ?   BLENDFUNCTION m_bf;
6 ~# {7 b. {! u4 A/ }/ d# w9 |. G+ L( v  \2 S6 R( T
   CBitmap cross,lantern;1 t5 ?# Z1 U7 A4 Y- `0 p" |

8 y. e. R' j; I/ Y' Q& U) ^   BITMAP bmp;
6 Q1 K: g6 R' n- X( l' h3 I+ d7 [
   int bmpWidth,bmpHeight;
% ~) t3 g4 y. V' L! Q/ h2 y! i" D3 K9 i# [: _
   CDC dcForCross,dcForLantern;
& j; ^% u: W  h1 ]6 r! @4 F
" |# G( _- ?& n3 Q+ {( g5 |   CDC? dc;
7 E0 n/ o& L1 G/ N; g: w" @) ^
7 K' X% h' L: G, I' u0 F   BOOL bShowLantern;, w& u: I9 f- Y4 F9 i

& n- e( u2 P, Z  e( W  ………(系统自动生成部分)( {% @$ Q0 X6 e9 ~5 Y6 g) a. H
+ Y) P" q* O+ [0 `) D" }
  }; 
* T$ o! n7 \/ _# b# w$ s1 g5 ]/ o* V* I4 R( k: I# w0 l
  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
5 Q' @4 E% Z$ ~( Z. u+ }9 ?  C' V  k+ z5 O
  BOOL CWipeImageDlg::OnInitDialog()
8 r! u* c' K& a; _
- C# s  q1 s5 K4 n2 ~" F  {
5 l4 [1 I5 p% ]! H5 g9 J" \$ J" J( P4 u% }
  ………(系统自动生成部分)
. V. s; |0 J. V/ c- R  U
% K5 K8 a1 G2 E  @) {; t, Q   // TODO: Add extra initialization here
9 R' W! O: }5 x' k& Y/ t
5 Q! @* n  g  }4 [! o" g+ s   //初始化全局成员变量
% X* @9 c5 L7 y0 ]3 c
/ k3 s/ f; s/ G' O) W8 F+ F0 P" Q   this->bShowLantern=TRUE;
& ?/ ]4 {  Q! [9 \" p+ U$ i$ j( C1 k; e: ], a2 {
   m_bf.BlendOp = AC_SRC_OVER;
( c, B5 h5 r. O; [" l7 M# Q$ P$ S$ s. d
   m_bf.BlendFlags = 0;
1 ~+ r+ Q8 Y; [& c9 [" b/ X  ~) `! L2 o9 [
   m_bf.SourceConstantAlpha =10;- m; B. h' ]% Z: ?# f$ d" J! S1 Q

: ]/ K) m7 j$ U( {   m_bf.AlphaFormat = 0;
, ?. h: m* B+ m  Y) F. \
) s; [9 o$ @1 q1 s: ?   //为节约篇幅,以下代码中略去对操作不成功的处理代码
3 T2 E) V3 \2 F7 l6 Q) x* d9 W) o/ Q  H% Z% k. U+ x
   if(!cross.LoadBitmap(IDB_CROSS))$ P: e. A' R! Z& U2 x9 G& P8 i5 H

; \7 ?* k: }  u" I- \5 O   {
4 g3 N* \2 M  e0 N- V1 E0 X6 p% m: v4 Q0 w# {, Y
   AfxMessageBox("装载位图出错!");
* l; L7 U8 N: c. }) I, c7 j; |; x/ j, O# h
   return FALSE;/ g% K+ f- U) n9 l' ?' [0 w
% e% U, w9 C7 W, v5 T6 u
   }6 ], F$ M3 K# \8 k

8 ]* R6 r9 z' m   cross.GetBitmap(&&bmp);
; ^7 C; j# u5 x* E9 z( z0 h1 {
2 H* k; B0 x1 |. A% y   lantern.LoadBitmap(IDB_LANTERN);
% {" V2 i* U' N/ u( T% d, I/ N/ k! x
/ ~" j3 J+ u* ^  z) S  J. A' A   cross.GetBitmap(&&bmp);
( z  D2 N; J9 o5 y. R4 |: L6 o+ k8 m" @+ W4 Y6 R0 ^
   //获得位图的大小信息
; P$ X( r3 ?2 H2 @) S
4 E/ x; \8 i; g0 o) t9 l- X6 V: w0 T   bmpWidth=bmp.bmWidth;
7 ^( _- }9 w+ k- D3 o! R5 {% n  m& Y2 y3 Y6 \
   bmpHeight=bmp.bmHeight;/ P( a! ~# G  H

8 u( u6 U) p! |  D; G  dc=this->GetDC();" v/ D( r7 C8 h4 |: h& }, ]) @
4 [1 w$ R; G3 b7 I" o" w& L- A
   dcForCross.CreateCompatibleDC(dc);  [5 E, b8 a* R6 R1 o
( }4 _: Q; m3 d% J6 L2 K
   dcForLantern.CreateCompatibleDC(dc);
, G* I( b' F. \( P# Q5 P$ L, Y' |3 P8 U( d8 q& i# G) y1 ~6 \4 q
   //将位图装入设备环境句柄
; X/ _( b2 e& i( ~/ v1 _2 v- E4 G: E  ], P( q
   dcForCross.SelectObject(&&cross);/ n- |1 J# U6 E0 q  S: {
8 k& X& u6 |/ [- W
   dcForLantern.SelectObject(&&lantern);
6 x& \' S9 b! ~2 N! a. O7 k) X- Z. d9 a; l
   //打开计时器. M4 \, U# e$ Y+ F& a" s
9 Z8 H8 b  s. v0 q: A5 a' b+ e, U
   SetTimer(1000,50,NULL);
1 d6 a8 W+ i& ^4 l5 l) A. Q9 v# Q
   return TRUE;
; Y0 K3 C2 K3 m+ O/ S
' f  H+ {( l8 }4 E4 C/ E- C  }& |5 o( p$ a' F0 L# ~3 J" N6 Q7 D

( f# E$ O( |% s+ x& F! i  void CWipeImageDlg::OnTimer(UINT nIDEvent)) G1 ^  r8 \8 \: }, E
+ o2 s( a% E. t1 l  L7 W' R
  {4 N: w* b/ V) a& T/ h

: I0 |/ j: f/ M( i. S. h8 Z, H   //图片透明度每次递增5点( y9 j$ e! I( t1 G

% G( u* ]+ c! i( J6 T8 H  r   m_bf.SourceConstantAlpha+=5;& N$ a: R. ]. ]+ S" r& F* Y

! Z( m3 ]- T+ O: s   //当第一幅图片完全可见之后,显示另一张图片) V$ F# n- B" A6 i

) [  A% x7 ~6 r" y( |7 {   if(m_bf.SourceConstantAlpha>=200)2 r/ a2 k  e3 n) B" b/ Q: ?# d
; D. k4 o3 p" P, H3 p8 a. B
   {
; n/ Z5 X( ~, d$ b0 z6 j9 G$ h, K0 y; @  {9 h- f; Y$ z
   m_bf.SourceConstantAlpha=10;
0 D% S2 R: l, `/ Z$ I% V% ?6 r. i- x( s7 r% Y# k
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
. Q! [+ K( h. A  G
; \) l  d2 @$ T   bShowLantern=!bShowLantern;- g' d: s& r  |, w# Z7 y" ]

1 X! N; T  t; d7 m0 P   }
& d5 @* N7 [- I0 P: F7 ?. Q) X9 G+ n! w6 q- r" W
   if(bShowLantern)
6 {) C+ k& k$ s9 s: I$ s# P' p
5 }) H& ?  u- C/ v. |- Z, \6 p   {! i0 _3 F. O" u) v# O- y/ \

6 U" [1 {- ~4 ^   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
) d, i4 M: m7 L8 U/ v8 u% T
3 y  P: `4 w, x8 D% F  l   }
8 I5 l9 N+ B0 |2 M4 p4 B5 t3 W8 u1 z: x2 D& e
   else
" `5 W8 R/ M# \% `! ~/ \7 N$ Y8 ]9 ?! @
   {$ k5 ^! N/ s% ]! z# v

1 Q$ H6 o$ Y/ t4 Y  |! S  u   //按透明度递增的方式显示“背景”图片5 o$ I% S$ c& Y! b: \  g
& `% T6 f# O. j5 u) \
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
, g* @& ~5 u. r3 `; R# K6 s4 {3 P$ [* Y7 b3 A
   }& C8 j" k+ U9 n2 t' p4 S& X! k

) g2 Q5 \4 B1 K2 c   CDialog::OnTimer(nIDEvent);
/ ^2 {  j. s* B& R$ \! Q9 S
/ e9 H% w  Y+ u9 [" \& w( `5 t  }
+ n# P$ G% @1 g
# |' x  T8 V( x# F
4 `5 \& Z, N7 z/ s2 C  编译说明) y8 K& V- N" X/ }! m
, s* r8 ~% ]/ `7 H

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

本版积分规则

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

GMT+8, 2025-11-14 18:11 , Processed in 0.017886 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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