📄 05.4.2 字幕变色功能的实现.txt
字号:
5.4.2 字幕变色功能的实现
读者平时在唱卡拉 OK时,应该注意到歌曲字幕会随着曲调的播放而有一个平滑的变色过程。如何在程序中实现这种效果呢?如果我们先把字符串输出到屏幕上,接着把文本的颜色设置为新的颜色,然后一个字符一个字符地输出显示该字符串,也可以达到一种变色效果,但不能达到平滑的变色效果。为了达到卡拉 OK字幕那样平滑的变色效果,我们需要利用 CDC类提供的另一个输出文字的函数: DrawText来实现。 DrawText函数的作用是在指定的矩形范围内输出文字。该函数的一种声明形式如下所示:
int DrawText( const CString& str, LPRECT lpRect , UINT nFormat } ;
该函数三个参数的含义如下所述:
. str
指定要输出的字符串。
. lpRect
指定文字显示范围的矩形。
. nFOl mat
指定文本的输出格式。
DrawText函数实际上是把文字的输出局限在一个矩形范围内。当输出的文字太多,以至于超过设定的矩形范围时, DrawText函数就会截断输出的文字,只显示在设定矩形内能够显示的那部分文字。利用 DrawText函数的这个特点,我们可以将文本设置为一个新的
颜色,在窗口中己有文本的位置重新输出一遍该文本,初始输出文本时先把矩形的宽度设置为一个较小的值,然后不断地加大矩形的宽度,这样就可以不断地增加显示文字的内容,从而实现文字的平滑变色效果。
文字变色是一个不断变化、自动进行的过程,这意味着我们需要不断地调用 DrawText函数,同时增大包含文本的矩形宽度。要实现这个功能,我们需要用到定时器,通过定时器来自动控制文字变色的进程。
定时器与我们日常生活中使用的闹钟有些相似,我们可以把闹钟定在某个时刻,当时间到达这个时刻,闹钟就会振铃。当我们听到振铃声,就知道我们定的时间到了。定时器
的功能也是这样的,当它到了一定的时间,就会发送一个消息。我们收到这样的消息,就知道时间到了。但定时器与闹钟不同的地方是,定时器是每隔一定的时间发送一条消息,而闹钟是固定在某个时刻,只有到了这一时刻才会振铃。例如,我们把闹钟定在早上 8点这个时刻,那么只有到了这个时刻,闹钟才会振铃;而定时器则可以设置为间隔 10分钟发送一条消息,这样,每隔 10分钟,我们就会收到一条定时器发送的消息。
利用定时器不断发送消息的特点,我们可以在响应定时器消息的响应函数中,不断增加显示文字的矩形宽度,从而实现平滑的文字变色效果。利用 CWnd类的 SetTimer成员函数可以设置定时器,该函数的声明形式如下所示 :
UI NT SetTimer ( UINT niDEvent, UiNT nElapse , void (CALLBACK EXPORT* lpfnTimer) (HWND , U工NT, U工NT, DWORD) );
如果这个函数调用成功,那么它将返回新定时器的标识。该函数各参数的含义如下所述:
. nIDEvent
指定一个非零值的定时器标识。也就是说,当我们定义定时器时,可以为它设置一个标识。如果该函数调用成功,那么这个标识将作为返回值返回。这就是说,如果这个函数执行成功的话,它的第一个参数和返回值就是相等的。
. nElapse
指定定时器的时间间隔。也就是指定定时器每隔多长时间发送一次定时器消息 CWM_TIMER)。需要注意的是,它是以毫秒为单位的。例如,如果将该值设置为 1000,那么每隔 1秒钟,就发送一次定时器消息。
. lpfnTimer
这是一个函数指针,并且要求是一个回调函数。在第一章中己经介绍了 CALLBACK的含义。这个回调函数的写法已经在上述声明中列出了。当设定好定时器之后,每隔设定的时间间隔,它就会发送一条定时器消息。如果在这里设置了回调函数,这时操作系统就会调用这个回调函数来处理定时器消息。如果我们将此参数设置为 NULL值,定时器消息,即 WM TIMER消息就会被放到应用程序的消息队列中,然后由程序中响应此消息的窗口对象来处理。
在这个 Text例子中,我们在视类的 OnCreate函数中设置定时器。在此函数中,设置
一个时间间隔为 l00ms,标识为 1的定时器。实现代码如例子 17所示。
int CTextView: :OnCreate(LPCREATESTRUCT 1pCreateStruct)
if (C飞Tiew : :OnCreate(lpCreateStruct) -1) return -1 ;
bitmap .LoadBitmap(工DB_BITMAP1) ;
CreateCaret(&bitmap) ;
ShowCaret() ;
Se tTimer(1 , 100 , NULL) ;
return 0 ;
另外,本例是在视类中对定时器消息进行处理,因此需要给 CTextView类添加 WM TIMER消息的响应函数,该函数的初始定义如例子18所示。
19~ 5-18
void CTextView : : OnTimer (UINT nIDEvent)
11 TODO : Add your message hand1er code here and/ or ca11 defau1t
Cview : :OnTimer (nIDEvent) ; '
可以看到,这个响应函数有一个参数: nIDEvent,这是定时器的标识。在一个应用程序巾,我们可以设置多个定时器,每个定时器都有自己的时间间隔和标识符。但所有的定时器都发送WMJB4ER消息,这时就可以通过这个nlDEvent参数来获得当前是哪个定时器发送的定时器消息,然后可以针对不同的定时器做不同的处理。本例中只有一个定时器,因此就不需要对此参数进行判断了。
因为需要使 DrawText函数的第二个参数,即显示文字的矩形范围不断增加,所以需要设置一个变量,让它的值不断增加,然后在程序中把这个变量赋给矩形的宽度成员,从而实现该矩形的宽度值不断增加。因此,在CTextView类中再添加一个 int类型的成员变量: m nWid曲。并在视类的构造函数中将其初始化为0。这一步很重要,如果不初始化这个变量的话,那么它的值将是一个随机值,在随后程序中对它进行自加或自减操作时,结果很难确定。
:对一个性进行自加或自减操作前,一定要初始化这个尬。否则,
结果是不确定的。
本程序将对前面己在窗口中显示的那行由字符串资源 CIDS_STRINGVC)定义的文字实现平滑变色效果。首先需要获得包围这行文字的矩形的位置,实际上,只需要获得这个矩形的高度就可以了。因为矩形的左上角坐标就是这行文字显示时的起始坐标。而这个矩形的宽度并不需要知道,它是由m nWidth变量决定的,是从O开始按某个值不断增加的。
为了获得这个矩形的高度,也就是要获得设备描述表中当前字体的高度,可以通过 GetTextMetrics函数来实现。
下面,我们就在 OnTimer函数中实现文字平滑变色效果,具体实现代码如例子 19所示。
例 5-19
1. void CTextView: :OnTimer(UINT nIDEvent)
2. {
3. m_nWidth += 5;
4. CClientDC dc(this) ;
5. TEXTMETRIC tm;
6 . dc . GetTextMetrics(&tm) ;
7 . CRect rect ;
8. rect . left = 0;
9. rect . top = 200;
10. rect.right = m_nWidth;
11. rect .bottom = rect . top + tm.tmHe工ght ;
12. dc . SetTextCo!or(RGB(255 , O , O)) ;
13 . CString str;
14 . str . LoadString(IDS_STR工 NGVC) ;
15. dc.DrawText(str , rect , DT_LEFT);
.
16. CV工ew : :OnTimer(nIDEvent);
17. }
上述例 5-19所示代码中,首先设置 m nWidth变量的值按 5个像素点增加(代码的第 3行).也就是说后面调用的 DrawText函数的第二个参数,即限制显示文字范围的那个矩形的宽度按 5个像素点不断增加。接着,根据设备描述表中当前字体的高度得到这个矩形的高度,并利用这些信息初始化矩形对象 (代码的 7 ~ 11行)。接下来,程序将设备描述表中文本颜色设置为红色,井根据字符串资源获得要显示的字符串。然后就调用 DrawText函数,完成在指定矩形范围内文字的输出。因为定时器每隔 100ms就会发出一次 WM TIMER消息,也就是每隔 100ms. OnTimer函数就会被调用一次,每调用一次,这个矩形的宽度就会增加 5个像素点,所以,以红色输出的文字范围就会增加一些,从而实现了一种文宇平滑变色的效果。
Build并运行 Text程序,将会看到一种很平滑的变色效果,而不是一个字一个字的变色。程序运行过程中某个时间点处的结果如图 5.22所示。
上述例 5-19所示代码中. DrawText函数使用的输出格式,即它的第三个参数使用的是 DT_LEFT.这是一种左对齐格式。我们可以再试试其他格式 (例如 DT阳GHT)看看效果。将下面这几行代码添加到上述例 5-19所示 OnTimer函数中第 15行代码的后面。
.
rect.top = 150;
rect.bottom = rect.top + tm.tmHe ight ;
dc.DrawText(str, rect , DT_RIGHT) ;
然后 Build并运行 Text程序,程序运行结果如图 5.23所示。我们可以发现 DT一LEFf输出格式是从字符串的左边开始,逐渐向右输出文字。而 DT RIGHT输出格式是从要输出字符串的最右边的那个字符开始输出,逐渐向左输出文字。
在这个 Text程序运行时,我们发现还有一些问题。其中一个问题是,当以 DT RIGHT 输出格式显示文字时,在字符串全部输出完毕后,应该让它从头开始输出,而不是随着限制显示范围的矩形的宽度不断加大,而慢慢地从程序窗口上消失了。
另外,当我们唱卡拉 OK时,会发现字幕会随着音乐的播放而平滑变色,当一句话唱完后,它会变成另一种颜色,表明这句话已经唱过了。那么在程序中要实现这个功能,需要判断限制显示范围的矩形宽度是否超过了需要显示的字符串在屏幕上显示时的宽度。而要获取字符串在屏幕上显示时的宽度,需要用到 GetTextExtent函数。这时完整的 OnTimer函数的实现代码如例子20所示,其中加灰显示的代码为新增代码。
例 5-20
void CTextView: :OnTimer(UINT n工 DEvent)
m_ nWidth += 5;
CClientDC dc(this);
TEXTMETRIC tm;
dc . GetTextMetrics(&tm) ;
CRect rect ;
rect .left = 0;
rect .top = 200 ;
rect . right = m_nWidth;
rect .bottom = rect . top + tm . tmHeight ;
dC . SetTextColor(RGB(255 , 0 , 0)) ;
CStr工 ng str;
str.LoadString(IDS_STRINGVC) ;
dc . DrawText(str, rect , DT_LEFT);
rect . top = 150;
rect . bottom = rect . top + tm . t mHeight ;
dc .DrawText (str, rect , DT_RIGHT) ;
CSize sz = dc.GetTextExtent(str);
if{m_nWidth > sz.cx)
m_nWidth = 0;
dc.SetTextColor(RGB(O , 255 , O));
dc.TextOut(O , 200 , str);
CV工ew:: OnTimer(nIDEvent) ;
上述例子20所示 OnTimer函数中新增的代码段 (加灰显示的部分〉首先利用 GetText Extent函数得到需要显示的字符串的尺寸。接着判断限制显示范围的矩形宽度是否超过了该字符串在屏幕上显示时的宽度。一旦发现其超过了,就将该矩形宽度设置为 0,让文本重新开始输出。并将设备描述表中文本的颜色设置为绿色,但此时,先前己输出到窗口中的文本的颜色并未改变,因此还需要再调用一次 TextOut函数,在原位置以新的颜色重新输出文本。
Build并运行 Text程序,我们可以看到当字符串仓部显示完毕后,会立即从头开始重新显示。并且对 (0,200)处的字符串来说,在全部显示完毕后,会变成绿色。另外,我们还可以看看如果把 DrawText函数的第三个参数设置为 DT一CENTER时的效果。这时,我们会发现文字是从字符串的中间字符开始向两边扩展显示的。以上就是模拟卡拉 OK字幕变色效果的实现。当然这个程序的功能比较简单,读者可以遵照这样的思路,去实现一个卡拉 OK这样的系统。
飞结
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -