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

图形擦除技术及编程应用

[复制链接]
发表于 2003-10-12 23:35:05 | 显示全部楼层 |阅读模式
周鸣扬
  n. W7 h* ]! z4 M& P( D8 ]" j* [# {2 p: S
  图形擦除是图形特技处理中最为常见的一种,在各种游戏中图形擦除技术有着广泛的应用。图形擦除在本质上是图形的消隐,即在两幅图片之间进行图片的平滑过渡显示。过渡的方式决定了图形擦除的不同视觉效果,其中最为常见的一种就是图片淡入淡出的更新:两幅图片由明到暗、由暗到明的循环交替显示。这种特技效果在编程中的实现,往往是通过DirectX技术实现的:DirectX Transform为我们提供了一个“Microsoft DirectAnimation Control”的类(在注册表中可以找到该类的注册信息HKEY_CLASS_ROOT\CLSID\{B6FFC24C-7E13-11D0- 9B47-00C04FC2F51D})供调用,以此实现高质量的图片擦除。不过,对于DirectX编程,大部分的编程爱好者对其程序框架难以适应,可以说,花在理解DirectX编程上的工夫要远远大于对图形擦除技术本身的理解。有没有一种更简单的方法,使用常规的编程方式来实现图形擦除呢?% a# {+ H1 C8 q+ I" B; c3 m
7 q; C" H6 j) o5 v4 `; P' u7 M
% s  {1 O* u' i7 M$ _' H
  解决方案
# [7 L: X, K7 J0 d6 e
0 b" B2 c% ]. Y2 a: [% H: K& @
2 m  B8 O# ]# {9 P) |& q/ s  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:  Q/ ?8 y1 X4 N' |9 ^8 Y- S/ n

$ V8 Q' c7 i+ W  D; e  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
0 l1 F: Y+ g, e' i4 K" [( N& O% ~6 r+ n4 E. v1 B0 v, P
  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
( S* {% ]0 s) l8 q; i& }4 X
3 g) w; E* q/ ?2 {  {( u% d9 h  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
# a) K" M1 R; c4 ^2 Y1 K7 c2 |1 n3 d& G7 R3 s5 m
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。
7 x, A1 {, Z5 x! h. c2 r; a0 f, J. g) G
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
' S0 W2 f* N- o2 ]' Y( A. L$ N
) P7 g1 [+ ^- B- J  BOOL AlphaBlend(
1 x  b( x1 [- p9 }5 g% p6 N$ j2 A$ Y: ]! J/ _
   HDC hdcDest,
- H2 I8 A5 n  v) z- m, y7 D0 {/ t$ V. D+ m. {+ T8 k- H2 R7 w8 B' k
  // 目标设备环境句柄
- h9 G. y" o( j; N' I  D  u7 o2 U# i9 y/ w0 [
   int nXOriginDest,
1 g% V+ G8 L3 f; K
% C( _1 q. M- _4 o/ u; _& w  // 目标坐标x
/ \0 @/ N% }$ w$ ?( m7 `) b/ S9 ]) M) B' g, l; t
   int nYOriginDest,
) H$ Q. P- M! ~1 h% h, b3 p- V) e& S7 g
  // 目标坐标y2 a& j* y" b5 \1 ~2 S2 n
5 p; X5 Y" g; U7 _9 k
   int nWidthDest, * N3 ]& n) R2 @& @, T. n* r

- W: F; M0 v5 e5 Y9 A  ?, u  // 目标宽度
+ X; g, s- G+ B7 H5 b+ I3 C; w) C& ^+ I
   int nHeightDest,
1 U) D( q! A/ G1 {3 p1 _) V5 O7 e8 ~0 r! L
  //目标高度
) u! O4 j: T+ a) T5 S# z& G) R
( [: ?$ T& W( k   HDC hdcSrc,
3 E  x5 t& o$ b$ ?1 s& i" Z
2 v. e- p4 m0 o6 c- G8 K  //源设备环境句柄
( @# X) B  L, n6 T. y* {& L- j& w- \. ~' o# x- L
   int nXOriginSrc, 5 k/ Y7 i9 D' Y% D( {% ?* m6 \
! i5 O0 O0 o. t* s* }9 [# t
  // 源坐标x3 [& `9 c# ~% c

  s8 L# Z2 i, n* f- @3 a( b6 O2 g- p   int nYOriginSrc,
. d5 q: D8 T* b& `; a: B4 v/ n; L4 o/ C# n: K/ z1 ]$ r; b
  // 源坐标y. u; d+ w+ i; ]+ T) _: y

: _% ?! d" e7 m) f3 {/ C4 w   int nWidthSrc, * G+ E( ?" r$ _8 f9 N: H9 h
' f3 a5 ?! R' t- T
  //源宽度3 g0 ]/ k8 E3 Y; S$ G6 \
8 p+ i. V7 }- j7 u* x1 ^
   int nHeightSrc, 2 B: t1 L+ [5 ]7 H) {

. o$ m3 V7 u  f/ S. y2 x! T. x  //源高度$ u, O' w0 ~; ^8 j- ?, W4 d+ T! r

- \9 {5 Z+ L, T: g: B; Y- w   BLENDFUNCTION blendFunction ' k5 P0 d9 u" j( V* Q; s4 t
; I0 S0 U" k4 G! B. r0 B
  // 合成方式具体数据结构
& C+ |# R# ~! B5 q/ V! q0 f: L" ?! z/ ]
  );
; i6 l2 X4 T+ V7 X7 E' ~4 R# N8 G+ ~; A- h% Y
  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
+ [. s$ s% Z( _0 N" f% U0 V
! J; x) K8 B: \+ q  typedef struct _BLENDFUNCTION {8 X! J" b% {5 N& U* n6 y
/ j0 G5 `) _" E- i, u" t
   BYTE BlendOp;
- y0 Z+ G  E2 u. r( m2 ?- ~7 T# |+ i
   BYTE BlendFlags;//必须为零
; g& C9 h3 J, j4 X$ i" z, N
9 F' H( Y$ j! n   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示( E- i( H% g- k# S8 O8 a5 e
; {$ N  d% Q* [0 k  u
   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA
" [" P! x" N6 x2 K
2 w, y: @! W! Z  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;  ^# u* y4 r* u. R

& U6 r+ f+ e) d8 |! J( K# }  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。- E1 y- |' a$ ~
, W; K1 z# |, w' F% e3 f! e

4 P, U- \. B' y1 h  编程实现
' N6 Q) l' Q" P3 i( P! q
1 f& s- N* n/ I: J! k, u7 m1 D' U% @+ R
  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
- I  Q6 ]7 Z& g6 Q8 `# ~1 T7 y7 H9 h" g4 b5 s, @
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:  h0 n) V1 ?3 s* w. q
7 O' W3 D+ s1 d, T- U
  class CWipeImageDlg : public CDialog9 G# k4 K1 ^9 v- c% L2 L

( ~) F- ^) w6 Y# ~  {5 h8 L& @3 U$ J9 z! M

8 t; P7 r2 R+ ~, k( D  // Construction1 t1 x7 i/ c: D

6 A: ~) `9 ~, m' j  d  O) [& }  public:
8 ~* }' G8 a* s
9 E. m9 S* A/ l   BLENDFUNCTION m_bf;
3 I7 q1 u% R4 h# W! T3 E5 Y
/ v) |* G+ y1 d( x   CBitmap cross,lantern;
* i4 n; E' F" I; B* W( r
6 l. i  A$ x9 I* S! i8 w& l  j   BITMAP bmp;6 F) v9 a: I: d8 H- @. f# c
% x. v4 `: J/ T
   int bmpWidth,bmpHeight;0 @. k- j$ R: ]" r4 h
0 V% `, c4 |. \- F6 Y, \9 d
   CDC dcForCross,dcForLantern;4 N  B, u5 a; K7 I, R% _" D
6 O9 w  e" W9 e, `1 u) {
   CDC? dc;% Z3 P1 M  V* m7 y, E" y
# P; D( m9 C0 Y7 o, d
   BOOL bShowLantern;
" X# }9 b7 G3 F( a7 N) c. f
5 _$ q2 V7 a5 @' s/ v! A  ………(系统自动生成部分)
: l# C, w5 P$ t( p, T, H3 y( W
7 Y- p" n, V6 G1 R! v  }; 
: P! `. G0 I; x% Z7 o; k6 R2 a$ ?3 E& w! u4 K( l7 w
  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
5 e2 F, U# |! u! Y0 b$ Z
1 Q( J& f. q8 ~" Q% h4 z  BOOL CWipeImageDlg::OnInitDialog(), D  k% K7 `, x6 e* c6 P
( t6 _2 A+ q  M. K+ x
  {/ N# f7 d# X, k5 f; T9 F, R

9 J# e, r- s; J/ D, c: m5 L! w7 E  ………(系统自动生成部分)
/ T3 v- L  o9 R  a( J& P$ b' f2 C4 K
   // TODO: Add extra initialization here2 v2 \$ i* m4 e, d" _. e

: m; f* Q* f" n* z+ ~9 t   //初始化全局成员变量3 G$ m- {6 G( T5 I
* M% a' |! v4 ^: e1 x' u
   this->bShowLantern=TRUE;
2 A5 j* M8 S+ s$ C, J
& `4 ~6 `. j4 e1 M   m_bf.BlendOp = AC_SRC_OVER;
- h- `# \7 J( x
$ g. }% @" D& F   m_bf.BlendFlags = 0;
9 E8 `' U4 X% y* Y
8 s- H! |. l$ G4 H   m_bf.SourceConstantAlpha =10;
+ g5 @  {- V( [; Z: i$ L6 x) ~6 `7 w) e2 Q; B2 J7 `8 S
   m_bf.AlphaFormat = 0;
6 O2 Z. m4 ]2 u! i" m- ^
; V& A* T, \" ^: `   //为节约篇幅,以下代码中略去对操作不成功的处理代码
6 N' A" c! p7 x/ K. _7 J
7 }& {! U+ D% N6 o: j  d4 {1 k# O9 W   if(!cross.LoadBitmap(IDB_CROSS))
+ ]( ]0 w! x3 Y
0 p8 r$ H# P# E4 ^$ V2 M* j4 i3 c   {/ }: d9 J% g5 i3 h% i( A% f

$ ]  e6 [( f. m   AfxMessageBox("装载位图出错!");: C1 O$ R* }4 _. V9 `6 V

" R" t0 o# I4 O  v/ q   return FALSE;) K( K9 B$ G' s0 Q" f
+ `& s! {1 G6 n3 J7 `! R9 X
   }
8 i+ c! L" ]8 J, m' f- M
  [% f2 E( N: g  t. o   cross.GetBitmap(&&bmp);
& J# E/ k& r+ }4 f1 q' J& f/ i2 C' I0 w
   lantern.LoadBitmap(IDB_LANTERN);* g3 Q8 l+ |+ t' [) z' I6 a
8 x* o; C7 S7 i
   cross.GetBitmap(&&bmp);
& B! p; v; ^7 d0 B* `% Z( G7 ]
   //获得位图的大小信息$ j5 P1 @! S) f
( s# }8 ~5 ^, ~. P* W( S8 Z
   bmpWidth=bmp.bmWidth;. x6 c+ d8 C$ f5 h: r
' W# Q  }% c* u5 V& n: X. Y% Q* B3 d
   bmpHeight=bmp.bmHeight;8 _; F+ p+ b) Y6 o' R* C
3 \- I. v1 V6 S) Q
  dc=this->GetDC();! j3 X, l% @0 O6 \) ^, F# T

& G8 \. z" b; b- S' B3 ?+ P   dcForCross.CreateCompatibleDC(dc);7 G* a+ O0 _' l) U/ E6 j' u* H

+ q  P5 @: |" q1 x& l9 }! A   dcForLantern.CreateCompatibleDC(dc);: p" J  B, w) ^

2 A& q! |! i5 ~# P% Q3 T2 t9 Z7 L   //将位图装入设备环境句柄
0 l% p8 \3 }) P, [, S7 j$ k* W( W7 O& k2 C* S4 ~4 H) e
   dcForCross.SelectObject(&&cross);
; \9 ~/ P; ^2 ~- q/ x/ u3 m, z' c
$ {  Z* L  L7 ~/ F1 b3 \   dcForLantern.SelectObject(&&lantern);3 Q. z& A4 ?0 S

- Z/ \5 N" a. b! N   //打开计时器
0 w+ ?! n) n$ C, @7 a( a- I3 y% ~9 N
   SetTimer(1000,50,NULL);" x+ _+ k3 e( A1 f5 A; U  }# f$ ^

3 o/ u# u$ C2 p! a; ]# O7 p- ~   return TRUE; 6 p. C2 B( D$ i4 u
3 o0 X1 u: d% v5 B( c7 K: e6 B4 T
  }
3 l) p2 p$ @8 k7 M4 X% y6 f9 k: F7 n6 f1 v
  void CWipeImageDlg::OnTimer(UINT nIDEvent)# a# H7 w8 t/ M8 X& L: |- O
/ e, C- O- x# P& k4 v4 h+ K
  {9 e1 O: z$ k+ E% r& U

. [' m/ m5 n4 S; o" X   //图片透明度每次递增5点
: z* W% E# n  }4 S* Z1 Z5 P% L# s* E5 \' C+ B
   m_bf.SourceConstantAlpha+=5;6 x' F6 p3 V. v. e1 Q# s- u
: M2 ^5 R0 `6 u+ O, d& v7 ^; a
   //当第一幅图片完全可见之后,显示另一张图片
7 e2 H. v6 i0 M! I/ Z5 o8 o3 L1 O% ~+ ~  m- i
   if(m_bf.SourceConstantAlpha>=200)
6 a0 ]( {, B. S. W6 s: P) o
6 h% U2 s# M: ^5 S- b/ o   {$ z) _" Q% U( f0 }5 y( n. F) o/ k% \

" w3 S) j5 Q2 x8 a. T8 O/ C   m_bf.SourceConstantAlpha=10;7 ]$ |4 U1 x9 {: f, X; P
  P; D; P4 S* p1 u# H/ E. y
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 # {3 Y2 f# X. l0 S9 M- K8 N
' k9 h8 c7 S. @& d: B( v! Q
   bShowLantern=!bShowLantern;. R9 ~8 k+ W. b) Q. {. r
0 \! W1 j6 U' A$ I% B/ e" V
   }
+ w+ \0 `0 l& j7 c9 b9 R8 X" r* @2 N8 ]9 G0 e
   if(bShowLantern)
2 q  g1 P* {% x- d5 Q! s# N* X" X: G5 S0 I6 I$ `" h7 Z9 Y
   {. e6 }- \* M0 |- S2 P7 G% ?
1 w5 u& d( Y4 {
   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);9 F1 p  Y) w7 }6 [, s% u2 p; D$ a6 P

) n; E" }' k$ ]: P: {+ v   }4 Q" x, s, ~9 k( v

) Y3 J3 B+ T5 {5 w  y2 k5 l   else6 p& Y# F7 f0 \/ B

& q& ~, S. s; q& t% X: _   {
/ V' U$ J' L3 A4 w4 v) h1 m
4 `% v5 [8 j' c  X" z( P   //按透明度递增的方式显示“背景”图片# L3 H* w7 s5 X" J" B3 |: Y6 m

$ W% c1 a0 q9 K: C   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
# Y& M9 b. B% z% W) P: N. p; U/ @
% g6 d* L* p, \7 ^& ]   }$ a% q; O. z% Y

; R% {% s9 q6 {( s1 `4 p0 V0 g   CDialog::OnTimer(nIDEvent);9 [6 S7 w0 R8 T- V
% c- S8 i9 Y4 _( d9 w. Z- I
  }6 Z1 i9 Q. H+ H( M* S  R: C

4 `; Y# k7 C) v; p3 ~7 \- [
4 p/ ]) L6 J; \, ~/ i: n3 o7 D( h7 a  编译说明
, L! h7 s9 M# t! }2 |0 Y9 v. J2 K. f% P& }% Q

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

本版积分规则

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

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

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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