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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬   V" N! v# K  w
, M4 Z3 g1 [& t9 B$ f  L. w
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?
, ^; L2 ~& u) ~- l# @9 @) s8 d
- \! T+ K% J  X3 U; a7 y7 \2 L$ [- m7 x$ T$ f4 q8 T
  解决方案
! Y. B& g' F; _" W$ R" a; P& D' H. @) R+ x

& c4 Y% G7 q+ S1 `; I! I8 s  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:3 K% a' ?% w" z- n8 v. G
. k+ F0 Z9 W' `: s+ X: B% y; T
  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
+ p5 D# C( K  N' o# k' }
: A# X1 y4 q5 \" `7 k- `: Z2 i  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
* |" R5 q5 S% R! c: s
- n1 u8 v& I0 w3 p  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue% l( x% D9 E9 l! ?
! x" y: T3 h+ _6 ]! |- b- h1 k
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
$ T3 h& b- o, V4 ^. w1 g8 f$ y1 t5 i0 r- e( S: ~' P
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
) U0 J* C2 h0 N  i8 B' a$ X5 A4 ^: K# |9 U$ ^
  BOOL AlphaBlend(
2 E4 I$ B9 w0 I/ c( I- U  y3 s$ N) P6 E1 m
   HDC hdcDest, 5 z. R, S7 H3 e1 @, G9 ^/ g
# J- s9 F, V, X8 y1 j
  // 目标设备环境句柄
7 x5 Y( A3 |) I% h' Q
9 a# d; k/ u9 Q. s# @% z; R1 M1 P   int nXOriginDest, ) {2 h. A% J: |! q; X5 E( w
* S+ @, H& G8 T6 q
  // 目标坐标x% W1 b. G" O' [7 L" ~$ G
3 x4 M& p( h, N& r' i7 q$ l
   int nYOriginDest, - M8 g; F! c* N* K1 _
9 C5 `! l) [, ]7 p
  // 目标坐标y' K( {% I" b( h7 k( p
, p' v2 p6 O) _; d
   int nWidthDest,
5 K4 q. c0 p; y$ ^
3 H/ ~$ T2 J( V" a  t# B6 F  // 目标宽度
6 ^: h0 [9 m/ y0 T
0 I7 V& k/ X. d   int nHeightDest,
  b9 N3 r6 b5 ]% P, p9 `: f) g/ a; h; ~# T+ ?3 h2 k
  //目标高度1 \7 p$ ^6 f2 Q+ {; _7 T- A9 c

6 x& Q4 E; D. |) ~; P. Q   HDC hdcSrc, 6 k/ v- N. R& [) d4 U0 s0 j( u

$ B( g# e# _+ c# Z, B) i  //源设备环境句柄
0 [: S! u- C- H4 s
7 s9 C! B% M' }; y   int nXOriginSrc,
0 D" y( O! e8 K& i, F3 `
$ S% t) b6 S! R3 e7 v  // 源坐标x) s! ^( i3 }; Q0 \- ?2 Q: Y3 j0 q

, {$ Z. o' a. `" O  e   int nYOriginSrc, - ?8 B: s/ [6 b' d# o- [

1 l* g4 j% \. _7 N* S; p  // 源坐标y" }0 U5 K; \+ L5 {! G1 \
/ I/ U+ s3 g5 x6 Z* s5 k) c7 K" |% \
   int nWidthSrc, . W- ~" g3 _- M) F, V

: G2 h2 s* D5 b# q# S1 e, ~# g: e  //源宽度1 s7 g$ j! z- G& S
+ G. B& R7 z2 F  \* I1 ^
   int nHeightSrc,
+ \4 v+ f' r/ L. ?8 D" A* D: |) T" K2 m4 s
  //源高度
6 M% O; h; \: O! A$ e! ]7 k+ G
8 e- \: ]9 D, i9 g3 d. k+ D   BLENDFUNCTION blendFunction
$ u. T2 d' P% C( i" N, C8 Y1 l* p: v( _4 ]! F* p
  // 合成方式具体数据结构
1 k4 j6 a* |1 N  s8 `0 j- z. b) f# p9 e
  );
- P4 ^/ k2 ]' E  ]: z; i: k0 R7 B
# @+ v8 E) r* h& F" c5 y" [  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:6 Z! x( Z# u  I; B
2 `4 ^7 M9 O- q2 r" }: s, T) x
  typedef struct _BLENDFUNCTION {; j8 w+ D6 r. x, x% Z
. A8 Z# l& I2 H- f- V: ~
   BYTE BlendOp;! n+ C, `6 l' `; T0 N/ i

5 Y2 g; U3 k  Q6 M! o   BYTE BlendFlags;//必须为零
7 A  H& P2 X. t& f3 q3 m$ |5 m. L/ P  C: g2 e4 ~
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示- p4 a, L$ e1 B: c/ u6 l

) c% F) y7 U6 P4 c   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA7 X' ?7 G2 |& s5 C' B* V9 r6 X

7 q  j2 }3 r( U9 ^. \- e# l  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;9 @- s4 K; I" {, U7 E, u. y

# `: c4 r3 j% z. M$ s  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
& K5 ^3 ]. }/ y
( {: \( X/ n9 L# O* Y
) z" e0 i& o: R  编程实现
% c+ E; e, o1 s( C6 V4 H+ s4 L. c* s/ C  E6 p

4 f  e& {& R4 M9 W9 _, m1 y  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。; q/ E. a- g& V+ t& Q: x

2 r' Y7 ^9 N# b  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:; y0 c6 X" S3 P7 m* Q6 W5 c

! S# }% T7 ]5 y1 f# Z( p  class CWipeImageDlg : public CDialog! f& O" D9 _& Z  F+ ^, M
+ c1 W& G- y0 Q( h3 P' X6 I
  {
/ v3 W/ U/ o8 K( ?, Z. t
) o9 w8 S# f# V, }5 \- \. q  // Construction
3 y0 ?! y$ O* A! u4 b
. H& b, d6 J6 v& j/ C# K  public:2 g4 I2 R0 `! s( @" `

( V$ p- h* s, ]' H$ l. ]4 P7 T   BLENDFUNCTION m_bf;) b# v  F# _3 h
0 o4 E7 m/ ^7 E' o! H2 f5 I
   CBitmap cross,lantern;% w: Q& @1 s  d. [; Q5 F# E, i
5 U- _3 e/ Q& g9 v5 j. f
   BITMAP bmp;/ |4 W0 s6 b9 h
8 h4 r/ h* z3 b/ {8 H" _" A
   int bmpWidth,bmpHeight;
! g8 }% O4 n) H& \4 o7 O6 `0 P' ^/ B" e+ T4 O. m8 j
   CDC dcForCross,dcForLantern;4 t& N) ?. ^% A7 ^

' K1 V5 T) C* S; t3 P   CDC? dc;
2 V( B0 i- i- u* R) ]1 p% L: [" M8 _& @
   BOOL bShowLantern;
8 a0 f. T4 H- g* d( N1 R( _0 V7 U& c$ r# |8 n
  ………(系统自动生成部分)
, b0 J. J6 u- x$ j4 y$ M7 T, s1 D' P$ O4 _& l* `
  }; 
, i6 K3 r% n8 n9 F6 [' w) P" z( A  B8 ]
9 g0 E7 V- I6 M  z& Y5 J' I9 G& z# O  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
# R0 L0 w; p1 y  ?) w% A
5 }6 t- [  G; L( [  BOOL CWipeImageDlg::OnInitDialog()
2 j! i! I: L( J4 n" a( k( j
7 a' N$ }/ D$ S4 v* {  {
) V6 O  I0 k, E9 a* K
7 Q2 d+ t5 v  c; t3 b7 i! u( s  ………(系统自动生成部分)) l4 x  d6 K; _. k
- }6 V0 r6 i7 a" Y: T  U4 _
   // TODO: Add extra initialization here
, w& q$ P& Q; c6 s- U) h' B+ }" P6 T$ t5 S* ?# D) a2 b
   //初始化全局成员变量9 N6 I* Q3 p; u5 r

" ]# H. r5 e3 c3 |! L" }   this->bShowLantern=TRUE;
7 X" n2 s0 l0 \  m) A6 e' H- {' l# h  x- r
   m_bf.BlendOp = AC_SRC_OVER;
4 U7 Z  j/ V0 ?7 ]
4 g4 `  ]& x. b& M* }   m_bf.BlendFlags = 0;( T0 o4 o% M& L3 C9 d" ~9 ~

8 h6 j/ Q+ a- {( d: m$ b: J   m_bf.SourceConstantAlpha =10;) n$ X8 p. v  k4 @, F
: J' c  c( M, K
   m_bf.AlphaFormat = 0;
$ _! Q: O/ U+ L/ v5 T% @
0 X$ j8 k8 i0 m# \. Z. L( u   //为节约篇幅,以下代码中略去对操作不成功的处理代码
- U4 {, z# g3 G. C" K
( i' p/ o% ]+ P+ X# F   if(!cross.LoadBitmap(IDB_CROSS))
1 J6 o4 x' W6 ?! y* Q7 w1 E9 O" T( N9 c
   {: ?- u. w. [, A
1 I$ e+ U1 ?+ z2 o
   AfxMessageBox("装载位图出错!");
5 d' v8 }7 \# r: r  w  m# S& r: C9 Q! T4 x! v) Q
   return FALSE;
6 \4 i4 G$ i7 `5 |
0 u, X& `# R0 Q- U/ M   }
* D6 s: T! ^& B5 H9 D' E; b5 ^+ ^* ?2 T7 ?' A9 o& r' [
   cross.GetBitmap(&&bmp);
0 D5 E" g' ?3 H& e/ [9 L* t- \" f/ `+ Z/ M* w
   lantern.LoadBitmap(IDB_LANTERN);
5 t$ D/ E5 W1 I) Q- B1 V% Y$ M( B6 p) i9 x, c. l4 H, b" Y7 x( \
   cross.GetBitmap(&&bmp);
5 R4 r; P' h/ m  _
$ r9 m1 z( ~3 O- T, l/ Q   //获得位图的大小信息! u: _% O$ b1 N  i- c
% G5 w. g/ \# y: q7 O; z4 q
   bmpWidth=bmp.bmWidth;9 g: |; ]8 w8 f0 n; A0 T8 P6 v3 d
0 v7 Q* L! r; {/ J
   bmpHeight=bmp.bmHeight;
4 m- S+ J, _. a/ x  n
2 s/ N: e+ [8 ~  dc=this->GetDC();
' z, L( V& t4 |
5 ^/ w7 C2 I7 {1 n& f   dcForCross.CreateCompatibleDC(dc);  A! g2 p+ Y+ d* a; Y; m  e$ l+ S/ e5 e
* X+ t5 d1 u: V4 V, I0 d* i- U2 h
   dcForLantern.CreateCompatibleDC(dc);
6 R9 X8 _2 E0 ?* j% J) n' e& I" v! s7 i4 J0 Q) F
   //将位图装入设备环境句柄' h3 z, D- t3 r. D' ~0 X' p

, T4 t; s8 ~* v3 N: z; B   dcForCross.SelectObject(&&cross);
- s* |8 x2 b' \3 |
1 j6 `8 V' M6 b1 a2 z- t   dcForLantern.SelectObject(&&lantern);
! B# @8 D/ W7 ?$ o' P! A" s4 P8 T$ G0 A2 w) f0 ^
   //打开计时器
$ g% Z+ @' o) {' ]7 _; C9 S
5 j, ?7 r. J; x" Q# Z$ \   SetTimer(1000,50,NULL);
  H7 d& Z( M' d$ i# R9 h$ m! H
' D" n! m: s( _; r. Q4 ^( w0 w   return TRUE;
. \% A9 J! b5 m, O2 {7 S+ j: K: `$ `' c' ~8 N$ X
  }
" C$ r: N8 D! l7 b7 F" F5 J3 {. q  O" s- [0 o
  void CWipeImageDlg::OnTimer(UINT nIDEvent)1 r( _" q0 I- s/ X( t& u' [( U

" Y. N' o% w* q0 Y# e/ ]! Z  {6 Q* D% Q& ~" b& n
* }* _& q$ n. K1 t/ E  o3 }1 P/ H
   //图片透明度每次递增5点
' u! m7 |& _; A$ j( q0 j. D1 I* J/ P( f
   m_bf.SourceConstantAlpha+=5;1 y( \0 ?; S' D' I& G

; `$ |# Y1 P. A1 R* ~   //当第一幅图片完全可见之后,显示另一张图片
% S: m7 @# t# e/ ^8 q) K4 F. U- ]! {3 ^. G( [, _  q3 |# o
   if(m_bf.SourceConstantAlpha>=200)
* g. L& o, y6 C0 V& R
' T# w( Z5 d& Z   {
/ I5 y; U) _' ?: D; T3 Y
/ x0 O9 Q8 F% x9 x9 D4 U   m_bf.SourceConstantAlpha=10;0 {( z* [# D4 K* G1 C5 g9 L" e  z! Z' i
& L7 R$ j/ Z- z1 p5 g) O
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
) J: q& ]& C4 T, n6 P8 Q
: z9 ]$ v! C$ s. _- m   bShowLantern=!bShowLantern;
6 O& W# m$ r: x3 O4 i3 w
6 x5 B# k" i8 m: n0 I) s3 A$ o   }5 {* Q8 G2 W6 U7 d& ]4 C! o4 O

1 j" U% t: v1 D1 h8 X# y   if(bShowLantern)
5 n# U9 s' I. L  x" n' A/ b$ N  }' Z3 t4 |; T
   {
8 P1 X; Z+ h) K- X# e$ @7 d$ \
& u% r! x* P1 ~8 y  O$ F) k; M   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);# k* u* k: g7 {0 P
! m2 @8 {2 g0 ~
   }
, ^8 v- o% `! w0 I/ u+ I6 e1 K
1 i" J% h  c+ h4 v5 A& l! j: ^   else( K+ k, u& O" A

' M: N) d8 k% H   {, P- w$ s/ D$ L4 O3 Y# b

. ]2 y* `" [0 e/ _) I   //按透明度递增的方式显示“背景”图片
* i( f4 p8 Z- C7 @7 v7 B5 Y; `  Z+ f
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);( k+ N0 ?* t$ T& x8 |: p, L
; q: l$ ~' I6 ^4 P" ]6 o
   }8 M, \. U: ^! M* k4 J
, R: ?: N3 \( z3 A
   CDialog::OnTimer(nIDEvent);# f* l+ q. s6 j7 L/ M# |

" S" L! e( @4 O. r  b8 B. H6 r  }
$ ~! [( e6 Q: ]8 X" b, A1 c
* c. n6 t- R1 u1 q0 o
& d# j1 D, b! o9 j0 k: e# k; t  编译说明
5 ^$ N' u3 K! ?1 i) n. b- @2 x; E: M" }# e$ [5 P8 t: r+ p
5 }* a2 @" O% r4 t$ v& J2 E% h
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2026-8-13 22:42 , Processed in 0.019341 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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