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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬 * n7 K$ U$ z' R$ V( ]2 k9 O. z

" ~! ^$ L: ^8 a( G& J# C' P  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?2 a1 c7 W' z3 O& M; [

8 M: s5 n0 B, J8 D8 w
4 e" n9 a6 D9 y4 T  解决方案) F. T: b. I: f- w3 W& s
8 h4 a9 }3 z+ ?
8 O2 I$ D/ Y6 }% {2 d1 I9 y5 f! J
  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:; y/ J5 t3 H% F  H0 _
2 g7 i8 U: y9 w
  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red . A# C& L- t% }5 ~/ {
4 B4 H8 i1 R' d
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 1 s& A( _9 f5 ^  ], c
8 c; ^. V0 l8 X4 [
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
. A+ L- J, F7 ]. ?
: o# @$ c% Z& s  t  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
. U1 m1 h, f. j, x6 {3 {5 E7 g: T" q. E( \
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
8 l" D: C/ a2 M0 {: `! P+ n6 e9 N9 V8 u& U- A8 V# z; Q
  BOOL AlphaBlend() I* X( I, K0 P4 q& H2 u# R
9 w( X5 V. u6 g* x
   HDC hdcDest, & `' m1 q1 P9 Z# s

$ h- R$ Z  E' R; N* H1 {/ m# @9 {$ n  // 目标设备环境句柄9 _$ s/ U* I$ [! c( D
& w, |3 q, b9 y2 q) @
   int nXOriginDest,
3 i0 h3 \6 P- f# A
2 K% Q: D, A+ \% K3 l' g5 `2 v/ a  // 目标坐标x2 w7 W$ d2 K9 @5 A, M
9 w; X$ i' N; G( R9 z( q( D% i
   int nYOriginDest, ( G% m4 H2 [! X3 [
2 j, `+ h/ N" U6 I
  // 目标坐标y
1 c2 {! A; r3 x% n
$ w5 W* Z7 [- ]% r$ D( p# S  D2 b   int nWidthDest, + b8 z' }( {& ?2 I

: R) D' n8 J# X# a2 b  // 目标宽度! c+ u& M  k) p$ }+ Z7 ?
" i* G  H, R2 }' O
   int nHeightDest,
; b+ I- c. d( \* u: G, N
* Z* E' ?" F/ l( t  //目标高度2 n+ s2 g, D" u& ?# U/ Z

( Q) H7 o  Q4 r   HDC hdcSrc, & s- W1 w/ q2 y8 x& e
" X. m& r, F% W/ [
  //源设备环境句柄
6 o5 j& T' ^/ @* ~5 w) F0 t2 j  m9 w* L. h% v: P8 ], [- T0 B
   int nXOriginSrc,
+ X2 H3 h+ s; \  w4 a3 M# e2 x; Z- w5 \/ L" d: M1 F
  // 源坐标x3 p0 d7 x  \% E9 E5 q- V8 r4 C

5 r5 W  y- D# u6 t   int nYOriginSrc,
* W" ]% B0 W# q0 o+ g8 I3 \3 v7 C  D* d+ l7 `; R; h
  // 源坐标y$ `5 D- \& L( l" \# F$ g
5 w  @8 u/ Z4 K9 x
   int nWidthSrc, ; `' _! M6 v& K! ^% e$ O4 x+ b

, h6 Z! x( ^! t( H3 M0 b5 r  //源宽度
0 T/ r, t; ?2 k/ C; X8 U. A1 k) E% y5 c% K
   int nHeightSrc,
8 y/ c1 ]5 i4 J2 C8 ?! C: q0 l6 X; O  j0 u+ w
  //源高度) s9 M7 Z. B+ D/ l9 D$ v9 t  a
$ e( w- O* i" e  h3 V
   BLENDFUNCTION blendFunction
" i) D7 d$ n: c6 {: b8 v" p# b" x# l" S5 c4 K7 j; H8 I
  // 合成方式具体数据结构
4 X7 z8 X9 ~* \0 t0 G7 _
( k2 p1 O+ q8 Z* J+ A  );
1 k6 V) B6 Q6 f2 J$ B0 M% L! _8 u- I
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
6 m1 X- E/ W* m) J
5 Y3 O$ B- R2 L7 \: ?/ a$ R  typedef struct _BLENDFUNCTION {: k; ?3 e$ q' d8 ^4 r

1 v( }! O. m2 h/ l   BYTE BlendOp;. k1 j/ o, m& Q. y
% g, P+ y9 I- M( ~  l: c* c
   BYTE BlendFlags;//必须为零  v# x: D* |; [* Q: {
4 p: ^+ B2 M9 A3 \0 ?" k1 [
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示/ V! x* o3 p: J
: p: n" Q6 L& x5 D/ ~( w
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
4 g2 p5 p. A" g& F! \9 U0 Q5 k$ g/ `, a" u
  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;" a; }1 {$ _, W/ J! u; S+ J
' u$ A! F  k9 g9 r- q0 b
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。
% m+ ?9 ~# Q0 ]1 @7 _$ Q$ K, h
* N9 Z1 N- m9 }* K: P
- S/ P& w3 Z/ U8 l  编程实现
4 L: e8 H$ J$ h% y- k! v7 ^2 x+ r) x0 `% S4 w; W( w- T

+ L/ i7 t. B3 K  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。* a* g9 u" q9 P8 X5 }8 ^, s# }
  C" N  P# G& r! U' s! |; Y
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
5 F$ e; B+ c3 T+ p) L$ I" l
' k: {6 _7 c) O- v* ^: l1 B% f0 d  class CWipeImageDlg : public CDialog1 r3 ~8 I/ I6 B+ w8 }& W- o/ S9 j

- L9 b' T7 m* b' ^/ h5 P  {
$ W4 C" B- V& d( r' H3 `/ R, _: F  \% n
  // Construction
8 E( E& ^1 k: U1 ]: [( {
- m: \1 i) u+ Z$ q+ h  public:
1 A( O5 u3 s( B* Q- W" `
, O0 r9 V+ w, `3 y   BLENDFUNCTION m_bf;
( x, X+ x, p  {5 x
& M% E. K% C* V/ l1 b   CBitmap cross,lantern;  \* [  T4 ~. p9 N7 }) J, ^
4 K& z2 G* O+ R4 S( V2 U
   BITMAP bmp;
' U3 A9 C3 U6 z% [! X% z3 Q( H$ \9 g6 t( S
   int bmpWidth,bmpHeight;9 h9 s  N3 T/ @; i' E1 t
4 [) F3 q* u6 P) e7 l; U
   CDC dcForCross,dcForLantern;
% b, g3 }( V9 ^+ R: V, s# d* |& x: B9 E
   CDC? dc;
0 z& t7 A' c1 ]& L% m% ~
; o0 F. T4 S! y, h3 X; K4 {   BOOL bShowLantern;
! h: o  {8 r# a  j7 f# a
# h5 x, u8 s' z0 @: |  ………(系统自动生成部分)! }1 n& _- i  t% H
2 _& S# T& T$ I- a0 Z6 s
  }; 7 x* ~4 x+ m$ {6 G; u# y

; }6 w% ?7 w) E& D) `2 w  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
+ A7 h5 E2 _$ E/ O7 f" G1 o# z0 z0 b! z4 N/ W0 |2 h3 d
  BOOL CWipeImageDlg::OnInitDialog()
, q. }* P- R+ y& C! n/ d2 `' l0 N! ?/ `1 m
  {& O9 s7 q! C+ `: l
: @5 w; ]" B% w) `7 w
  ………(系统自动生成部分)
- h$ a6 e3 m% s( S9 x2 I" A  S/ p' s0 a2 I1 B
   // TODO: Add extra initialization here
/ s& W7 Q+ m6 N5 Y
1 R) n/ M5 C! s8 y% }' j, [7 `   //初始化全局成员变量
1 `! S' F+ u* S3 {4 n7 }6 F) g# ^0 a9 V1 Y! s5 L
   this->bShowLantern=TRUE;
2 g- k  ~* s6 E2 T% V8 Q
& g( `8 a5 P( j" ^% k2 y5 ]- c   m_bf.BlendOp = AC_SRC_OVER;) K+ h. @' K) G/ i/ ]2 L3 F. a
1 K* O8 P# h. i  L6 L
   m_bf.BlendFlags = 0;& \4 q5 q1 k5 P
; T, b9 E3 S$ e0 [2 ^
   m_bf.SourceConstantAlpha =10;
1 j. t' c+ E0 R  P0 i) V( W' [; }$ z) ?% N; z
   m_bf.AlphaFormat = 0;$ _& w+ \. Y8 L/ h

0 K7 t* ]' O$ \  p" b   //为节约篇幅,以下代码中略去对操作不成功的处理代码
) W! r: W5 f3 W% H7 y# h' L/ b. c
5 C! ^# k5 m$ H, F+ _6 @4 o8 u- |   if(!cross.LoadBitmap(IDB_CROSS))
7 P* n) t4 i" l1 U0 H
" ~" N4 n- K" j) Z   {
0 A2 Z6 A7 Z- c8 A( V
0 b/ \, N- k$ e3 v( K# s7 i$ ]6 V   AfxMessageBox("装载位图出错!");
8 o4 i9 W4 C/ ~5 l5 t- h
6 _. Z0 ^. D& ^# n   return FALSE;
( A0 `! t: H- r" Z3 B6 L& q: a4 {4 a$ T
   }
; v9 D* q* v) z: w  d3 r/ K% A; i3 R
   cross.GetBitmap(&&bmp);  ^, J3 s) Q& W4 H; v8 h

3 {- h+ Q+ _  M8 M- a   lantern.LoadBitmap(IDB_LANTERN);* M& L5 a1 v" b# B
0 d- U. m4 U3 u6 L: J
   cross.GetBitmap(&&bmp);+ B. ?% f) ]5 ?2 c
7 C+ P' J2 }9 ?
   //获得位图的大小信息  _3 e. o( {5 @- `: R
5 X' f* v/ F& _, l: ^! A7 r7 O
   bmpWidth=bmp.bmWidth;$ ?9 j8 @# D! W+ V( P8 u+ \: [: v

2 O& M+ j/ J1 P, Q- V! u, C   bmpHeight=bmp.bmHeight;! z  Q2 h, n" H+ i* {

% ]6 V% `) l% {- y: j& _' {  dc=this->GetDC();
; D' O( J7 m3 |1 ^9 }
0 C. v1 {3 h1 X' t   dcForCross.CreateCompatibleDC(dc);
1 A+ h/ n- q0 r& Y7 q8 f8 [* v% p0 ~. h9 D5 A/ X
   dcForLantern.CreateCompatibleDC(dc);2 y2 ~! P$ @- r/ N( w2 k: [

- l5 r& {( p! M2 ?   //将位图装入设备环境句柄4 k' j4 b: o8 D/ n1 u4 C( D/ T
) h; n$ b+ z+ p8 p* m' ]
   dcForCross.SelectObject(&&cross);
) B3 G! s6 e' @
5 o$ H- @" Z" ~- F( o! W) }   dcForLantern.SelectObject(&&lantern);
9 A$ _. `4 N1 z7 `- H8 }  }
( b. i( b$ k; y& S* e" W  X   //打开计时器1 a( ?( g! |7 s2 u& g) [7 @

# j; F$ k& ~8 n) O9 P$ u: m   SetTimer(1000,50,NULL);' l1 J1 a2 b& T' j, u
- b8 m: L0 ^% p3 z+ H9 S2 z
   return TRUE; + H/ f2 Q% l8 X7 \8 p7 d- a4 k/ w

4 J7 X0 H9 |. R+ w, S6 c+ ]  }" [$ G5 t; l7 p0 h/ K
7 N; U- G* T3 q1 S
  void CWipeImageDlg::OnTimer(UINT nIDEvent)
6 q5 A# X2 H" z4 Y5 l% e3 @/ Z+ ^: }" ~4 n; v+ a, }# p
  {
7 y9 s: N+ o" b, T# X; A3 ~
! M* y9 S: o, u* \" m- }& H   //图片透明度每次递增5点
2 u7 o- m2 D/ h  G
6 P2 {7 w1 @' l1 s( e2 n   m_bf.SourceConstantAlpha+=5;( }& p! p0 ^/ {. @0 d1 ]* G4 I

2 T) g' \8 g: J   //当第一幅图片完全可见之后,显示另一张图片
6 \8 _5 u3 v. y  V9 d3 @- l( u& M, }& [* }) c7 V4 L# q
   if(m_bf.SourceConstantAlpha>=200). y( k; j+ W3 i' r
; L+ b7 ~9 ^% ?5 s8 r
   {
/ h$ @0 o3 u3 i2 @7 C" P& v  E4 [; H& K+ |* {* i9 O5 s  [5 f
   m_bf.SourceConstantAlpha=10;
: Y5 Y2 ]2 [+ v6 I8 I- N0 H8 E# d
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
7 {8 a) K* S! I- Z' |) G4 y& {. N8 d+ I
   bShowLantern=!bShowLantern;+ [/ ~( e' d( |4 r0 E
+ \2 }* d+ j2 U& T8 z" k
   }* S6 N9 |: Q9 ]4 S* `3 V
# U1 c- V9 X. L7 B; l& N. w9 u
   if(bShowLantern)$ V1 [' L) m) |9 Y

  i* |9 K$ m8 X% @- g% ?& F: k( Q   {
, j8 C- F" }  t. U' N
: c9 C+ b1 H3 T. p6 V8 k- a   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);
- U4 t" M7 f6 C  b, |
% k3 ]9 X6 W' [0 O! x8 @& D   }- C7 R; m! p9 z7 q

# V& h: A' e1 N6 M   else. E5 Z) W  g, a$ j% F

0 j( K* ]  b6 q+ u3 P+ `0 H   {$ b& s3 L3 C( m' k# g
/ A. X8 r1 K7 W1 J0 j, v
   //按透明度递增的方式显示“背景”图片
7 b6 |. S+ D( e8 m5 {* X3 P5 ^3 `9 ^6 ~1 Z
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);9 j: ?4 X, C. G, V2 c& k
! F* K! V% `2 ]2 U+ Q
   }' _/ W% G1 i' [

, y9 ^2 w) L" e. B! a   CDialog::OnTimer(nIDEvent);5 |! N5 g) u& ~) a3 _3 M
3 s5 j* y* u( ~9 O) ^
  }
" C: x! I0 o! Y' o) N8 i& ?1 c" ?9 B& b8 @- y+ s' ~5 }) A) [
5 X" r$ h" k5 c& i7 a) ~
  编译说明
2 O3 _  j, T. o9 H% }# K6 v% Y" N6 t. ^$ p4 a& c8 D5 G

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

本版积分规则

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

GMT+8, 2025-5-5 04:51 , Processed in 0.014901 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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