找回密码
 注册
查看: 4881|回复: 0

图形擦除技术及编程应用

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

7 k1 ^4 ~) y6 e: W' f# Z5 Q& k
7 n6 J; l$ c, E" C2 |1 F4 g. S  解决方案4 @2 H& D  K: Q1 D/ d
) d# |6 W6 s, w1 D, N

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

" I! j/ @3 l8 h* C) y  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
2 h9 [2 t& r, t8 V; Y5 Z' B1 |/ h+ g4 U4 c% j7 R. U9 V/ @+ y2 s
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green 5 }5 {' y* I1 a; X
; _/ z7 j  N% ~& o5 f+ a) R
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue& ^' x2 V$ m. J. a. G

4 m. h- a) u: e& ]  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
" b( T% q8 M8 X# [! d& Q& f" ?" J& b
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
9 e" {# y8 [/ W* y7 R6 ]6 ~# t8 t" Z7 a8 {/ {
  BOOL AlphaBlend(
: f# r. r/ v2 t" u8 L8 b! ?5 f
9 U$ W, s; ~% s/ ]- g. `$ m   HDC hdcDest, ! F& J+ K: u; E
3 v* D- g0 K5 p2 G+ d% p5 N7 Z0 ~( d
  // 目标设备环境句柄0 r4 L% c2 G+ z$ a+ v8 |7 l3 @

6 o1 a; K4 F5 F# j0 {  U   int nXOriginDest, 1 k2 S& b# \7 s$ N  ?

& U( }, T+ G3 u, z7 V# I  // 目标坐标x* U2 H$ a" l# v. u  B4 K- z% L4 _

- ]" ]7 |0 X  `/ n   int nYOriginDest,
+ c: u( k5 ~% W% ?# D$ D
/ T. Y' w5 _' ?  // 目标坐标y
+ [4 L0 v) W# L6 j- B5 _) R8 y8 A6 g/ H) U" i* d. X$ `3 L
   int nWidthDest, 3 x8 E! F0 {8 o7 x" v

9 ^' l/ U9 A9 i7 U% _8 l( w  // 目标宽度; w/ O- Q: p5 X4 |
" f3 ?) O- H; F8 O: _- @
   int nHeightDest,
. m; _9 S5 V4 A) x: _( {
1 L& z2 u7 o$ C  //目标高度
, d8 ]) G5 E) G4 U- `
# i2 y' h% e2 ?1 V) l, M   HDC hdcSrc,
0 f: y7 ^! I8 q8 l  U5 ^
/ X) w' {* |% L- N1 w9 \  //源设备环境句柄, i' @9 G+ I) S8 X
0 f% S8 R( H( t) T4 R
   int nXOriginSrc,
8 ~2 [, m8 d& p( D0 ~% M
6 x1 h6 C9 T# D: f5 I( J' y8 g  // 源坐标x
3 }$ c. f! h* \% @: e
  w0 b4 q3 v; K" e  R   int nYOriginSrc,
$ C- c/ q5 e1 `6 ?' K6 ^5 ^( B4 M7 d% g* l; P/ `
  // 源坐标y' ]9 m" n) u) u2 Y, r
" n% r& m& l# r1 ^
   int nWidthSrc, ( Z' Q7 _4 q$ @/ y/ B5 K3 e
/ t% N5 J0 w! j; B
  //源宽度
" [- a& U, T) B6 ?6 e2 o4 R
. H+ i  ~3 i9 S+ H   int nHeightSrc, 5 S& {6 D% V) o; J

; X4 Z3 C& w# z0 z$ A0 h( v  //源高度# m  |/ ?- u0 O* l4 ~, C2 k

( `) @6 M; S3 k8 j4 ^8 E   BLENDFUNCTION blendFunction
$ e: _0 K% [+ j" ?! c3 @8 T# c$ r+ ]3 U5 u3 ]& M" M# e% A$ L0 K1 ~
  // 合成方式具体数据结构. T4 o4 M5 ~) ]
. f% i# a9 N8 {9 @/ y! w
  );
3 H- L1 y# j! g' }$ ?& ?2 G$ ~! @1 H% k& z$ B  B
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:* u! {; A: `0 z# B

  O' K' s! Y1 O  typedef struct _BLENDFUNCTION {
/ W) `3 Y& s" [3 M2 m) m4 ~6 t. G  W
) ]. k- g- c, j8 v1 n. F' O   BYTE BlendOp;9 W" `# v4 ?. {# P" f3 _. |+ t
3 w4 N$ x  t  ?1 t9 M5 t5 h
   BYTE BlendFlags;//必须为零
0 ]+ V$ k- L6 A% [) b% j$ {$ E2 z4 F+ d" F9 w
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示4 Q; P, t+ F. v) N
  d) s& N2 h; J4 i5 h0 _
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
) h, r9 }- m; d7 V8 s6 |
' s5 S: b0 ^" {' @1 _& x  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;4 G. N; H) e' q

1 ^; X0 X5 f' |% ^, C  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。+ N' s9 B$ f# p8 ^1 e

4 m: d9 _) D- u- g3 N% O$ f* V
! s0 _6 f! b/ k  p7 n  编程实现
- T0 P! W4 [# M  B
- y5 c8 O5 r2 ~( ~/ R; b- a
5 L# V" ?" Y; Q& X" c1 J4 b, X- e  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。  q1 [6 P  n! e) }! M7 v% A( X7 Y
# _/ R& }; e: ~1 M
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:( u+ O4 e6 V& K  d

' x- `' g5 \) F1 \/ z5 s0 i1 G  class CWipeImageDlg : public CDialog
. Z$ ?% E4 E/ _" ?5 ]. k
, Q& W0 F/ Y& Z* f1 E1 I' s  {4 ~; R$ B- T6 T& Q, K
7 Z- S$ N) {1 o2 d0 g
  // Construction( R7 r% U0 {9 N- ^2 p3 p- D

& {$ @% f: Z( E" h0 i4 ?  public:
; ^% D6 n$ F! G2 n+ D; T. Q" `* f" k: r7 n( q( D3 T. }
   BLENDFUNCTION m_bf;$ F: ?; J8 c5 {3 L4 L! ~

3 K( A. t6 }6 l& O) h  r   CBitmap cross,lantern;. @: t" O9 D9 R% s

+ c& }$ H% k+ N& r8 O   BITMAP bmp;
1 U% h% y4 q/ {( V6 N! P8 K3 C  {. ?, b- ^
   int bmpWidth,bmpHeight;
5 o+ g3 Q' V0 X' E
# Z9 u3 }2 D+ Y3 t, t- `# b   CDC dcForCross,dcForLantern;
. t, z# K' o$ O! G% o7 V3 ]1 k* @
( y" ?  W  d, O& e   CDC? dc;1 T4 _# M% m3 k

: P' {: I" ?/ M   BOOL bShowLantern;
0 G: u, l7 v* M3 u0 R, ?: Y
5 q9 U( V9 F% r. x. P  ………(系统自动生成部分)+ p* v, b! u" N/ Y

9 b2 U9 o/ i  ?) f* [# P  }; ; Y9 z1 T% C8 S
( X) g  ]- ?# Z' m
  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:1 }4 ?) o! ~( W3 ?

9 g8 X  M  R/ P: x  BOOL CWipeImageDlg::OnInitDialog()- j0 k& ^  |4 e: U, w

2 C$ }0 O& Q) J$ C( L  {
  n* w1 F  f% w& m1 T- a
! b. H3 [# S* U+ n; v/ X3 Z& z  ………(系统自动生成部分)/ t. K4 X* M& Z( g
# s6 d7 W+ A, E
   // TODO: Add extra initialization here
6 `' @: V: H8 M
6 G$ i: t+ y: J, @  }  t   //初始化全局成员变量
  e" Y, I1 M8 h
; o+ X6 b  X4 B, M- S% V' ~   this->bShowLantern=TRUE;
$ p% q/ i; \3 {0 [# L9 u/ x' U2 }" N9 o! {6 t% l8 V6 e. X
   m_bf.BlendOp = AC_SRC_OVER;
3 S. [$ i4 V; Y/ f2 Y, R( z' \* C8 ]5 U7 t. {+ ]* s% y( ]$ R, _
   m_bf.BlendFlags = 0;  J# S& I1 Y3 [3 s( B* }- E! n

' Q7 K# O2 t6 t0 f6 |+ \* J) q   m_bf.SourceConstantAlpha =10;  u4 R6 C, P8 D4 P# I
1 m! B8 ~: F5 J  p- G" _/ U7 e
   m_bf.AlphaFormat = 0;  d5 j1 Q" Y- `& c! d
/ e8 B" }6 r& {" j
   //为节约篇幅,以下代码中略去对操作不成功的处理代码
. C1 m8 M1 a2 m6 N
: O  b  `) L9 g4 n  A3 V   if(!cross.LoadBitmap(IDB_CROSS))
2 d( {& ?) c$ b7 i" a$ \$ e" D& }* E, R* ^  x
   {* S( V) A% P& o" z2 S8 y
( F+ @; H9 F& p" W2 p
   AfxMessageBox("装载位图出错!");- s) B) m* g8 i% n* J

6 i( T" u- P5 n4 |9 t6 f   return FALSE;3 e4 U0 x& k! ?
( \  R, V$ h- L1 q. L$ @
   }
; C0 I# k; o: C- j  u5 j" |2 I3 H3 Y9 d2 P, U
   cross.GetBitmap(&&bmp);3 h& M0 c, y- C0 y$ C/ c1 W1 h
- t. l  Q/ [4 k! j$ {; ]
   lantern.LoadBitmap(IDB_LANTERN);# h1 Y  \. I# E/ \+ Z

" I6 z9 B; a, J   cross.GetBitmap(&&bmp);* Q0 ]/ E- c2 b* l4 C$ V

* [' m9 O$ I, W: G4 {8 d$ O+ `   //获得位图的大小信息( c. s3 p: z% W" \. `- w* i& k
* {5 e. P7 X( Z' Q$ ~4 N) f# w
   bmpWidth=bmp.bmWidth;
' {& v7 u3 o" F& T, n7 H; q+ T3 O* u4 Q
   bmpHeight=bmp.bmHeight;
/ [& s9 T! p3 X+ J) U' R/ I7 G, d. S' w6 I+ g9 H1 B; p! f7 [2 C
  dc=this->GetDC();# E) k3 U. d; o  P
; P0 U6 ^0 S3 b
   dcForCross.CreateCompatibleDC(dc);0 B' h! y! e  P  o
* L$ c1 F0 `: t7 I) f) t
   dcForLantern.CreateCompatibleDC(dc);
; y9 B! C; `9 ]! ~) R
7 |  C/ D( ~0 k5 M- ~2 R   //将位图装入设备环境句柄% T. \' Y. a. @' P! d# @$ ^6 H

3 l  q- G7 X8 Y8 N" s. T2 T   dcForCross.SelectObject(&&cross);# z! D( X: F/ f3 |3 x# {, b

5 Y% i$ e" [. L" K   dcForLantern.SelectObject(&&lantern);
; u6 y# a8 _. i2 [+ _9 Z/ L  p
4 q3 R# E' s4 x) o5 n  A. |3 s3 h   //打开计时器
. M4 ]6 X9 j. X6 z& d/ }0 G
4 g4 S: Y, T3 z6 _   SetTimer(1000,50,NULL);7 d# \' ]& g1 r8 N2 r8 O

% q% b4 }5 ]5 }   return TRUE;
/ d; V# `) n* h) D1 X
- Q, X0 _0 f7 I. Y* a( a  }
/ V8 J4 \7 l5 ~- I& Y
5 B) w+ Z) U! @9 @  void CWipeImageDlg::OnTimer(UINT nIDEvent)2 V" v! ]' s5 b) n3 U) H
  ]7 ?/ P* j  ~  p/ {2 N: b
  {
/ _" b, w6 G% C! f5 y% q, [- O9 k7 P/ y
   //图片透明度每次递增5点
; b* v3 A. d, C5 N
7 \/ h7 N9 M3 |2 Q/ M# Y   m_bf.SourceConstantAlpha+=5;7 X7 U, S1 E- E6 u
# r" B/ f# y% ^9 c5 c& Z
   //当第一幅图片完全可见之后,显示另一张图片
) a6 K: k0 S5 P& F
% @' x# h+ Q' a( W; x$ T, m* O   if(m_bf.SourceConstantAlpha>=200)
* i$ p/ X6 y3 k/ T9 T
' L( [2 }$ e/ C# P) r" @   {
4 u9 Z% }" p. ~- _. D7 p: L5 R% P6 F( N5 H9 G3 v. o) X% E: e% t
   m_bf.SourceConstantAlpha=10;# h9 P/ _* j& W7 z; R- C
2 m; D  y% H: n2 c. N
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 
' l; h  d, F9 s/ H0 }# M2 ~! k) j/ D: O2 H( x
   bShowLantern=!bShowLantern;
" x1 J( T* m' ]8 S" n" U
, G  Y. u( h6 ]: |1 O   }8 @) o7 l: {2 U% x1 D) g3 Z

3 J% R' G" E5 |" h6 r3 o   if(bShowLantern)
# l# W/ w3 {, C: [0 j- Q+ X* `- x8 _$ y" Y5 s1 d, m3 O. z2 w
   {
9 Z/ |$ ?9 \) w1 T: Y6 j. p) A
! s1 h3 O1 Z- A& G" d( [( J/ C* p   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);+ M( y! @( y; z

9 H0 ~9 l6 n/ `! x3 S. U   }
2 b# i" Q8 u: Q
; c0 S+ g- d8 `- q( j/ x% J9 f   else
" Z. a* |- p0 k3 d* J+ [/ x5 ~( l! |* V: @, e$ n& w
   {
' c/ f  G) k1 [0 _
/ ?. X* U8 I2 U/ Z   //按透明度递增的方式显示“背景”图片/ [+ E& b4 T' a# _% `
; S$ d3 ?5 e; y- D# y) ?/ T# f
   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);1 P2 j! l3 _  B; y

; D" y- G2 }/ d# @! I0 i   }
) `% ]/ v4 y8 P, b; v% p3 Y! m/ ?+ _9 t
   CDialog::OnTimer(nIDEvent);6 W$ ^9 B# B9 G+ t4 n% _8 M

6 k- p% G& r  Y& k  }
2 d) e" `) M! N( K0 R6 r! ~
. @2 J6 V( o; _$ R  n0 \  w( R# E& R! v1 d+ k, v2 u
  编译说明
; x- E4 q8 a$ ]) N: m) q- W& x' U9 g" C' c+ e6 x

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

本版积分规则

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

GMT+8, 2026-10-2 03:46 , Processed in 0.018216 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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