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

图形擦除技术及编程应用

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

% ^' J4 O; ^7 f. |; |# U  解决方案
% N/ d6 R$ m1 h2 Z3 Z. u* v1 n# _$ q0 [$ `6 e& N
- V; H5 l0 `- u* W
  不同于Windows 95中的GUI(图形用户接口),在Windows 98以后的版本中,GUI增加了对Alpha Blending(通道混合)的支持,Alpha Blending在概念上最为明显的就是对“通道”的应用。熟悉图形处理的朋友对“通道”这个概念并不会感到陌生, Alpha通道是用来表示数字图像的透明度,改变各种通道的特性,就相当于改变各种基本颜色的浓度。通常情况下,Alpha通道使用8位(Byte)二进制数,可以表示256级灰度,即256级的透明度。假设我们想要在目标区(Dst)里显示一个像素(Src:Alpha通道值为Src.Alpha),并且要求系统进行“通道合成”运算,那么,进行合成运算的具体公式为:! }7 R1 s) ?# W- C4 u
1 A* `/ n2 `/ Q) x0 }# m
  Dst.Red   = Src.Red+ (1 - Src.Alpha) ? Dst.Red
& s" N+ R3 o; G
3 ^7 j2 q; l( m9 o( q0 G) U' b  Dst.Green = Src.Green+ (1 - Src.Alpha) ? Dst.Green
- I' ?& m0 {6 S3 i' R7 {4 ~# N) N( _' {& D
  Dst.Blue = Src.Blue + (1 - Src.Alpha) ? Dst.Blue
. ~; o; G0 A1 ]& T9 y9 R) Q, c' x: B
  从上面的公式可以看出,在进行合成运算之后,更新显示后的目标区域颜色值(RGB)并不完全是源位图的RGB值的拷贝,而是源位图和目标区域进行了“合成”之后的RGB值。和BitBlt函数的像素运算不同,Alpha Blending强调的是源位图的透明度,正是利用这样一种合成运算,我们能够达到图像“透明”的效果。. B& |; I3 |/ M# F& z
" d6 O) t$ v. U: r4 W* u" P
  在VC中,系统提供了AlphaBlend 函数来实现位图的通道合成运算,AlphaBlend 函数主要用来显示透明或半透明的位图,其调用格式如下:
: n2 e; `- Y( S  u  J0 D& D+ ?, Q! B' y* p6 {+ ~. [& [
  BOOL AlphaBlend(' \, N, M6 N7 @7 y7 }7 f2 n4 a

( @+ j! G2 o9 W4 k( s8 `   HDC hdcDest, ; a0 s& b! D0 E: ^3 `
0 Q! h& K" m5 P0 ^, ~$ `; Z
  // 目标设备环境句柄' G! T1 ~6 Y0 d) G) d

: V+ ~9 S. }5 d, y9 R   int nXOriginDest, ; Q* E* Y6 a9 y0 s1 {

) s. l. V4 O; |  // 目标坐标x
7 i, \5 I9 m7 w" ^  W" [+ g1 O7 K3 O- h' `" o7 p
   int nYOriginDest, 7 [, I  _, w* {
/ P* J9 K9 I" k, O4 n7 j
  // 目标坐标y/ G+ W/ A, [$ O1 T5 k* d. w
$ e: a% }" e( j/ Z9 K4 ]
   int nWidthDest, - J7 G' Z1 K! L1 W

" |- |+ g* Z7 F  // 目标宽度
" H8 d+ a: o: o% \; l. V; T8 v
% p; e7 T, d; H; W% P2 \4 m) Z   int nHeightDest, % I7 `) g; T$ {1 \, A

3 W6 |8 {$ h2 {7 u. l+ u! A7 c7 f  //目标高度
8 g" ^- T) P2 g' Z# P; R; Y9 v7 w. U- n' z; a$ n* t
   HDC hdcSrc,
- V# e' p$ x- Y2 K8 n- Y8 T2 Y( c; o( V) s+ m
  //源设备环境句柄
! e6 W! b) ~% o- f4 X& b
: P+ D- B+ k5 U& F# C5 {1 E/ a   int nXOriginSrc, 4 S, ^* O+ t; v6 t0 a0 Z/ k/ F

0 ~/ T# H; q4 ^6 P9 @  // 源坐标x% R& y. ?  S4 T8 @1 K; k7 @/ x( f

0 N" ^, ~8 f" W   int nYOriginSrc, ( R! ~$ u4 _0 B" Q# _) k9 y6 L
4 x8 ~9 p9 T. ]4 p6 |. B: F
  // 源坐标y
0 g* H- J! s6 J8 T5 X' l8 p1 h
& J3 E; Z. G: u* a) B5 l9 Y   int nWidthSrc,
3 y5 k! S; ^2 [/ j  g: E5 y' n4 y& z# C. s
  //源宽度$ V& w# F; ]4 c* T' x1 o
1 G: W- Y0 p1 c; u% P' E
   int nHeightSrc,
4 \& W; N) M9 w8 i, c+ P3 I; z% G2 d' J' H. d( R( G4 g8 k
  //源高度
* X; g0 ~+ P! K8 n7 G0 h3 m2 W9 l1 S# d' E2 K8 ^
   BLENDFUNCTION blendFunction & \' Y, }" w. H, b1 e  R/ p
" W5 n( Z# |" c6 L4 A8 @" A" _2 `
  // 合成方式具体数据结构4 ^) \( x/ a* U) `6 |! y1 H$ ]
3 Q, v% R1 Q7 w, R5 Z8 _* g
  );, Q, W2 t# S" [

. r7 f* o% o9 j  BLENDFUNCTION定义了在源位图和目标位图之间进行合成的具体方式,其具体数据成员及含义如下:
8 L, F2 r, C( G& f' H; C; X8 @0 [1 ~' [, p9 z+ K! D) T% T
  typedef struct _BLENDFUNCTION {
6 h  _3 ]3 U' r7 L2 S5 {  ?
+ {2 U0 n$ F6 z# n9 [+ t   BYTE BlendOp;5 {( @: u1 D+ [
7 z6 k1 I5 W1 w$ O& k5 x' y/ ]
   BYTE BlendFlags;//必须为零  `% ]& S! }- I5 ~+ L6 L
. g( @* A% i" g1 S# d% Z
   BYTE SourceConstantAlpha;//位图使用的透明度,0为完全透明、255为正常方式显示
" W* a7 j0 t6 \  V* z
+ W% Z  H- G! _2 L! \   BYTE AlphaFormat;//通常为零,如果源位图为32位真彩色,此值可取为AC_SRC_ALPHA* \: B% ?# t2 L# v

( m. J+ v4 \% a% v  }BLENDFUNCTION, ?PBLENDFUNCTION, ?LPBLENDFUNCTION;
4 h# ~- r/ `, o3 F/ @0 V8 D6 l* ^& W0 a0 c6 g- e
  由上面的函数说明我们知道,AlphaBlend能够以特定的透明度来显示一幅位图,那么,如果让AlphaBlend以不同的通道值(从0到255)不断地交替显示两幅图片,这样就实现了“擦除”效果。0 k$ U( |8 q2 ^/ Y- m

. B3 b' f) n( R9 w* H' T9 @  R1 ^* u' E* v
  编程实现
  Q* d9 G' B' P3 k2 _2 l0 ]; X% F- N% K  L0 @+ ~2 N* `2 N: x- P
- ?! Z$ o, B. R& A& P: G  U
  了解了上述原理,编程中的具体运用就不会再是难事了,下面以在VC中为例,说明这种图形处理技术在编程中的具体实现。
$ |, y: X! I0 [" v+ [* X+ ^5 a2 |
  首先在VC中新建一基于对话框的项目WipeImage。准备好两幅等大的图片(IDB_CROSS、IDB_LANTERN),并将图片引入资源管理器。在CWipeImageDlg类中加入以下的全局成员变量声明:
0 Z1 u( [6 i" z5 c$ N. c9 K
$ {8 ]* ]$ |/ M. R% r5 X2 M  class CWipeImageDlg : public CDialog
3 `$ T$ E; n" V) ~+ [. J2 e
* S! k# M5 a6 C  {
2 t7 N7 e6 v2 X& H* h, P2 N9 e' s- [) ?# N* y
  // Construction6 ?0 n2 x% u1 v9 K! A7 x& E
. D6 a2 j: m% |
  public:; |+ c4 I; y5 {$ `4 v
* \% N8 k$ I# N; U; K4 k
   BLENDFUNCTION m_bf;
& ?/ T/ ~& _) H- G; g9 Y8 ]4 g7 {, o/ P' Y+ {: K  m, Z
   CBitmap cross,lantern;% t! c; y9 O2 k+ V2 @. l' [! \
, ], |7 H/ ~7 ~' Z$ [; }+ ]7 w# E( H
   BITMAP bmp;
' d) o4 g! P* M% ]
, Y5 k" q( o: F0 `) P5 K- B   int bmpWidth,bmpHeight;
0 H, G3 x! B  z$ u( Y$ F1 ~
  Z0 k+ E+ y) M5 Y$ ~* i   CDC dcForCross,dcForLantern;7 P2 L  A' K* {7 [' \5 _/ H# Z

( n0 h# M/ J( F0 m1 T: o$ ^   CDC? dc;
7 F3 R3 t3 W0 L$ Y, K1 L$ f3 Q) G' \- H. I
   BOOL bShowLantern;
" j0 S# F1 |8 H, h  [1 M
3 @& X. f3 ]4 H, I  ………(系统自动生成部分)! Y) N9 `+ x# m: P7 T4 j. y
' I/ G8 n+ l* i0 t9 Y3 r
  }; 
0 X4 o0 j5 l9 Q# x+ Y, D7 |* @7 |
& {. D- }" F7 t  接着在类向导中加入对WM_INITDIALOG和WM_TIMER消息的响应,其响应代码分别如下:
. @5 X1 b6 \8 s- b7 f
/ Y" B: [+ f( f- M  N! j' l  BOOL CWipeImageDlg::OnInitDialog()
4 P# e5 Y5 I& l, B; M. S" X3 H1 R* l: E6 n
  {9 w& L( ^$ R5 t) G
. U# H/ l; q! l, P9 ~
  ………(系统自动生成部分)
2 k, m* |3 y; F& L5 l" D4 s
( B! i: v- M" j! K% L   // TODO: Add extra initialization here
+ ~- N$ ]4 N% k' C( t
( ?$ a! D) x* q4 K   //初始化全局成员变量+ P: R3 x% t3 |6 c5 ?: P$ n

! u5 m7 J2 J& C5 ?6 r   this->bShowLantern=TRUE;: Q$ f0 `9 y( }8 Z
8 d5 q) B5 L: V) p% H& X5 h9 Q0 t
   m_bf.BlendOp = AC_SRC_OVER;1 g: ]& v0 e7 l  t7 I9 e* f
% [6 C& f4 p6 k$ z: x. g7 _; K
   m_bf.BlendFlags = 0;3 O  M  X! l- \( n0 y3 t6 j

; K' \9 a6 ~( L0 B: V9 B5 Z6 T' t. U   m_bf.SourceConstantAlpha =10;
4 c1 ~$ K0 {, u' q* ^4 r7 r4 N# _6 e& `, [! C- g% u
   m_bf.AlphaFormat = 0;
' i  D9 M5 X/ z& Y+ {7 k+ C& w& ~9 J$ t- a1 J  ^
   //为节约篇幅,以下代码中略去对操作不成功的处理代码# T  }& Z$ t: {' A  f- X# a
  e  {9 S1 o, w: U0 l# N0 p1 h
   if(!cross.LoadBitmap(IDB_CROSS))4 [; k6 p& b8 L8 `4 ]' B

$ {+ H. g3 h& _" x   {5 i8 L, v* {3 w& T; M( |

+ y. B3 Y" P5 R8 l9 n   AfxMessageBox("装载位图出错!");3 ?/ G: `; m. B; I( F8 n0 r) A' }
" y. v9 y2 C. [) z' h( T6 X: ^) W
   return FALSE;
' x" Q: F; A8 N' t# D9 F0 ?+ n1 G) r! D+ x: ?* [/ @' ~: ]
   }1 E; l& W5 l( ^6 b. C$ T: p' n
1 Y! {: q6 `& {; E" y, Z7 [. A' j
   cross.GetBitmap(&&bmp);- Z! Q& Y- f. v8 ]
# R  G$ X5 e$ n; a
   lantern.LoadBitmap(IDB_LANTERN);$ Q9 n6 H" K7 B$ e$ O# C, c
3 `4 r7 x4 Q- {3 ]' E
   cross.GetBitmap(&&bmp);
) @( V3 g: {- L: {9 u: Y
5 K3 `4 G4 L" `9 t% z# h   //获得位图的大小信息
& [) H' d* W3 R4 u
9 h% u- a. v# A) J4 [   bmpWidth=bmp.bmWidth;
8 K. Z7 Y& j0 c0 S1 R% k* e0 @& K. u! r" i
   bmpHeight=bmp.bmHeight;
! _' g- }$ ^0 I$ ?" p0 d1 u# T/ a, |
  dc=this->GetDC();8 d8 x2 _5 q$ q+ b7 ^# F
, D+ c" s. v' F/ Z
   dcForCross.CreateCompatibleDC(dc);
5 E& @5 m( K! E; B6 B# u" C, m9 q' w9 t0 }2 y) V& c7 s$ I5 f$ Q
   dcForLantern.CreateCompatibleDC(dc);8 r( x+ V) Y: m8 T( G! `# E
4 U" B( k; @; b% J& n$ N) n
   //将位图装入设备环境句柄
% S- ]7 K& k+ p, N$ ^# F( a# X0 K: ^6 [; E& ]8 W; w8 x
   dcForCross.SelectObject(&&cross);
* ?% Z$ P& A# e; y$ h) x1 F7 @% E3 a6 Z9 c
   dcForLantern.SelectObject(&&lantern);
: I2 h7 P' B0 E, o* j# C( B
' D' [0 `, o/ q+ D& z   //打开计时器
' V7 R: G& `0 X" \
, b8 S0 B; k4 h% L1 j   SetTimer(1000,50,NULL);9 g/ n$ |$ A$ v& f3 P: `

0 r8 Y) s# Q, }5 Y' X! v7 O/ u/ e   return TRUE;
9 \" b9 O% N, {  L: _* i8 T
% H8 a" G) {8 w/ e0 r2 d% {  }) O$ C" Q! I8 `  y/ {% J$ S7 T
1 D4 ^8 k! w2 j% {
  void CWipeImageDlg::OnTimer(UINT nIDEvent): @) U' |* Y  g; q3 _) c

7 Y  r) Q8 V0 b( y, V  {2 f$ E  Y3 [- S1 B/ K# x! C3 g

. W% R% I, W" \8 z   //图片透明度每次递增5点
. |4 R0 @4 G6 E* |8 b. i% h) b- x$ |3 E2 `5 `# {
   m_bf.SourceConstantAlpha+=5;
+ n# H! D7 L& \! d* K8 S
0 W, Y/ w& ?  e! W1 E   //当第一幅图片完全可见之后,显示另一张图片
9 z( k4 }( s  D+ \" _' d) y& }* Y9 {7 H
   if(m_bf.SourceConstantAlpha>=200)
' y* Q  z. `: I# @) J, U) r
% N7 q! {2 @0 U4 u1 O   {
) c/ H& U5 h* `* k8 J  [! l( t4 _
- _: g/ Y$ X2 U% N; }$ [   m_bf.SourceConstantAlpha=10;, D/ m$ }/ d$ Q! z; ^, r8 G
, N( l3 C& |  y+ G/ H1 W6 Z0 D: w/ T9 Y- V
   //将bShowLantern做为显示标志,确认应该显示哪一张图片 $ M5 [. z: S  X% h# n3 O  X1 \* M
3 t) g* i5 q$ @& D
   bShowLantern=!bShowLantern;, E" N3 D3 _3 y- p3 Y! p- I" e  w
4 `$ ]/ [  D/ \% z, F1 O0 C
   }
! O, f! f0 ~- \* z: n8 s: d
/ {$ h- z6 t9 \+ u1 d   if(bShowLantern)
9 ^3 m7 X, H! g! X5 P4 i9 X& V- Z; A0 S% A/ L4 j
   {
$ v& Y, d6 h2 F" @( D
3 w8 ]& ^8 {+ K- q   //按透明度递增的方式显示“吊灯”图片 AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForLantern,0,0,bmpWidth,bmpHeight,m_bf);7 h# Q7 V% n7 n7 X1 \
3 J/ i, T* _" J# q* x
   }4 R% v( i! X- A, e9 W

! x8 R' l& [" D( q   else
# K' n5 l  }5 ?* X' a9 K: y
1 G' {* i4 E" f6 {1 R  _* n   {2 L0 R8 x2 Z3 R% F0 f: w5 F

4 P2 I# z3 q% p7 v& F6 L! \   //按透明度递增的方式显示“背景”图片8 Q; C, _( x: o) |' T

; S% `- r$ E# g   AlphaBlend(?dc,0,0,bmpWidth,bmpHeight,dcForCross,0,0,bmpWidth,bmpHeight,m_bf);
( A$ N0 l3 j7 m
+ A- i- L3 B, t- o7 ]/ g3 @   }+ C7 x. s3 \: Z$ y' {( e( s" l
2 I) C$ G0 t) n
   CDialog::OnTimer(nIDEvent);
( _% ]' P3 q  O
/ X/ g. E/ ?! a! Q. I: M$ _  }% |5 H, r4 v, f, ], ]
& x0 ^, m. F- J/ c6 D

% |: }4 C  e4 L  J  编译说明
5 O" _1 z9 t/ {( K
1 T2 ]% t; N, ?( F* ^- M! t5 y; Y- d- w0 Q7 Q5 ]- F
  由于AlphaBlend函数是在“Msimg32.dll”(对应于Msimg32.lib库文件)中定义的。所以,为了避免LNK2001错误,在编译前应该将“Msimg32.lib”文件加入FadeImage项目,然后运行上面的程序,你会发现,在或明或暗之间,你的两幅位图已经出现在屏幕之上了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2025-12-13 10:09 , Processed in 0.019591 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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