除了经典的矢量图形游戏之外,游戏开发人员都使用图形图像来在视觉上展示游戏的图形部分。本文将介绍如何加载和显示图形图像。
本章内容包括:
- 位图图像的基础知识以及为什么它们在游戏编程中如此重要
- 位图图像的内部工作方式
- 如何开发在游戏中使用的通用位图类
- 如何在一个幻灯片放映程序中使用位图类来表示幻灯片图像
位图图像的基础知识
Windows游戏中的图像是用位图(bitmap)表示的,位图是一些矩形图形对象,包含一些小方块(称为像素)构成的行和列。
位图的得名是因为行和列决定了怎样将像素映射(map)到屏幕上,而像素本身是由位(bit)构成的。
位图中的每一个像素都是一种纯色,因此可以将位图看成是小彩色方块的一种矩形排列形式。
在Windows中支持两种位图,设备相关位图和设备无关位图。设备相关位图是以某种特定设备决定的方式存储的,而设备无关位图的存储方式使得能够在任何设备上显示它。本文及后使用的位图都是设备无关位图(DIB:device-independent bitmap)。
在Windows程序中使用位图
要想在Windows程序中使用位图,有两种选择:
- 直接从文件中读取位图
- 将位图存储为一个应用程序资源并从内存中读取它
直接从文件中读取位图
Windows程序打开磁盘中的位图文件并读入图像数据,然后使用位图图像数据来创建一个GDI 位图图形对象,可以在设备环境上绘制这个图形对象。
将位图存储为一个应用程序资源并从内存中读取它
将位图存储为可执行程序内部的一个资源,意味着一旦编译了程序,就不需要在程序中包括位图文件了。这种做法的优点是能够将游戏发布为一个单独的程序文件。
深入学习位图
要想在游戏中使用位图,必须对它们的结构有一个基本的了解。
位图的结构
位图的结构如图所示:
这幅图解释了每一个位图都包含3个基本部分:
- 头部
- 颜色表
- 图像数据
头部
头部包含属于位图的整体结构的信息,例如其宽度、高度、每像素的位数等。
颜色表
颜色表包含位图的调色板,它是整个图像中使用的颜色列表。
颜色表对于8位位图极其重要,因为它最多描述了图像中的像素使用的256种颜色。相反,24图像的颜色完全可以由像素本身进行描述,所以它们不需要颜色表。更具体的来说,24位图像中的24位被分为3个8位值,分别对应每一个颜色部分—-红色、绿色和蓝色。
图像数据
图像数据是存储位图的实际像素的地方。
例如,如果一个位图是10*12,那么横向是10个像素,纵向是12个像素,一共是120个像素。如果它是一个8位图像,那么每个像素都需要8位(1个字节)来描述其颜色。而对于24位图像,每个像素需要24位。
因此,对于10*12位图的8位版本,图像数据包含120个字节,而24位版本占用360个字节。
当然,所有这些图像数据都是在GDI 函数加载图像并开始使用它之后自动处理的。换句话说,只有第一次从文件或资源中加载位图时才需要关注位图的内部工作方式。
一旦完成了加载,就可以使用一个位图句柄来将其绘制到设备环境中。
开发位图类 Bitmap
创建一个类,包含加载和绘制位图所需的全部代码,然后使用这个类来创建位图对象。
位图类 Bitmap 的工作原理
Bitmap 类的思路是:提供一种从文件或者资源加载位图并将位图绘制到一个设备环境的方法。通过将这些功能结合到一个类中,我们就能在游戏中创建极易使用的Bitmap 对象,并且可以隐藏处理位图的各种烦杂工作。
Bitmap 类有以下需求:
- 从文件中加载位图
- 从资源中加载位图
- 创建纯色的空白位图
- 将位图绘制到设备环境中
- 获得位图的宽度和高度
Bitmap 类 源代码
Bitmap类的设计和之前的游戏引擎GameEngine设计类似,分为Bitmap.h 和 Bitmap.cpp。
Bitmap.h
#pragma once
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include <windows.h>
//-----------------------------------------------------------------
// Bitmap 类
//-----------------------------------------------------------------
class Bitmap
{
protected:
// 成员变量
HBITMAP m_hBitmap; //位图句柄
int m_iWidth, m_iHeight; //位图的宽和高
// 帮助器方法,用来释放与位图有关的内存并清除位图句柄
void Free();
public:
// 构造函数和析构函数 3个构造函数分别对应一种创建位图的不同方法
Bitmap();
//从一个文件中创建位图
Bitmap(HDC hDC, LPTSTR szFileName);
//从一个资源中创建位图
Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance);
//创建纯色的空白位图
Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor = RGB(0, 0, 0));
virtual ~Bitmap();
// 常规方法 create()用来处理加载位图数据并将其创建为一个GDI 对象,3个Create分别对应3个构造函数
BOOL Create(HDC hDC, LPTSTR szFileName);
BOOL Create(HDC hDC, UINT uiResID, HINSTANCE hInstance);
BOOL Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor);
//提供将位图绘制到设备环境上的方法
void Draw(HDC hDC, int x, int y);
//Getter方法 获得位图的宽和高
int GetWidth()
{
return m_iWidth;
};
int GetHeight()
{
return m_iHeight;
};
};
Bitmap.cpp
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include "Bitmap.h"
//-----------------------------------------------------------------
// Bitmap 的构造函数和析构函数
//-----------------------------------------------------------------
// 默认的构造函数初始化成员变量
Bitmap::Bitmap()
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
}
// 从一个文件中创建位图
Bitmap::Bitmap(HDC hDC, LPTSTR szFileName)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, szFileName);
}
// 从一个资源中创建位图
Bitmap::Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, uiResID, hInstance);
}
// 创建纯色的空白位图
Bitmap::Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor)
: m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
Create(hDC, iWidth, iHeight, crColor);
}
Bitmap::~Bitmap()
{
Free();
}
//-----------------------------------------------------------------
// Bitmap 帮助器方法,用来释放与位图有关的内存并清除位图句柄
//-----------------------------------------------------------------
void Bitmap::Free()
{
// 若位图句柄有效(即存在)
if (m_hBitmap != NULL)
{
//删除GDI 位图图像并清除句柄
DeleteObject(m_hBitmap);
m_hBitmap = NULL;
}
}
//-----------------------------------------------------------------
// Bitmap 常规方法,3个Create()和Draw()
//-----------------------------------------------------------------
//从一个文件中加载位图并将其创建为一个GDI 对象
BOOL Bitmap::Create(HDC hDC, LPTSTR szFileName)
{
// 清空以前的任何位图信息(使用于对不同的位图重复使用同一个Bitmap对象的情况)
Free();
// 打开文件
HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//检查得到的文件句柄以确保顺利打开文件
if (hFile == INVALID_HANDLE_VALUE)
return FALSE;
// 从文件中读 位图的 文件头(文件头包含位图文件本身的信息)
BITMAPFILEHEADER bmfHeader;
DWORD dwBytesRead;
BOOL bOK = ReadFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER),
&dwBytesRead, NULL);
//进行检查,确保正确读取
if ((!bOK) || (dwBytesRead != sizeof(BITMAPFILEHEADER)) ||
(bmfHeader.bfType != 0x4D42))
{
CloseHandle(hFile);
return FALSE;
}
BITMAPINFO* pBitmapInfo = (new BITMAPINFO);
if (pBitmapInfo != NULL)
{
// 从文件中读 位图的 信息头部
bOK = ReadFile(hFile, pBitmapInfo, sizeof(BITMAPINFOHEADER),
&dwBytesRead, NULL);
if ((!bOK) || (dwBytesRead != sizeof(BITMAPINFOHEADER)))
{
CloseHandle(hFile);
Free();
return FALSE;
}
// 存储位图的宽度和高度
m_iWidth = (int)pBitmapInfo->bmiHeader.biWidth;
m_iHeight = (int)pBitmapInfo->bmiHeader.biHeight;
/* 计算biSizeImage填充回去,是增加的代码
(因为无压缩BMP文件的pBitmapInfo->bmiHeader.biSizeImage 里面的值不一定是图像的真实大小,
可能是0或者随意的值。所以需要重新计算
*/
pBitmapInfo->bmiHeader.biSizeImage =
m_iHeight*m_iWidth*((int)pBitmapInfo->bmiHeader.biBitCount)/8;
// 复制图像数据,调用CreateDIBSection()以从原始位图数据中获得一个GDI 位图对象的句柄
PBYTE pBitmapBits;
m_hBitmap = CreateDIBSection(hDC, pBitmapInfo, DIB_RGB_COLORS,
(PVOID*)&pBitmapBits, NULL, 0);
if ((m_hBitmap != NULL) && (pBitmapBits != NULL))
{
SetFilePointer(hFile, bmfHeader.bfOffBits, NULL, FILE_BEGIN);
bOK = ReadFile(hFile, pBitmapBits, pBitmapInfo->bmiHeader.biSizeImage,
&dwBytesRead, NULL);
if (bOK)
return TRUE;
}
}
// 读取数据的过程中发生错误时释放位图内存
Free();
return FALSE;
}
//从一个资源中加载位图并将其创建为一个GDI 对象
BOOL Bitmap::Create(HDC hDC, UINT uiResID, HINSTANCE hInstance)
{
// Free any previous DIB info
Free();
// 找到位图资源
HRSRC hResInfo = FindResource(hInstance, MAKEINTRESOURCE(uiResID), RT_BITMAP);
if (hResInfo == NULL)
return FALSE;
// 将位图资源加载到内存中
HGLOBAL hMemBitmap = LoadResource(hInstance, hResInfo);
if (hMemBitmap == NULL)
return FALSE;
// 锁定资源,以便访问其原始数据
PBYTE pBitmapImage = (BYTE*)LockResource(hMemBitmap);
if (pBitmapImage == NULL)
{
FreeResource(hMemBitmap);
return FALSE;
}
// 存储位图的宽度和高度
BITMAPINFO* pBitmapInfo = (BITMAPINFO*)pBitmapImage;
m_iWidth = (int)pBitmapInfo->bmiHeader.biWidth;
m_iHeight = (int)pBitmapInfo->bmiHeader.biHeight;
/* 计算biSizeImage填充回去,是增加的代码
(因为无压缩BMP文件的pBitmapInfo->bmiHeader.biSizeImage 里面的值不一定是图像的真实大小,
可能是0或者随意的值。所以需要重新计算
*/
pBitmapInfo->bmiHeader.biSizeImage =
m_iHeight*m_iWidth*((int)pBitmapInfo->bmiHeader.biBitCount)/8;
// 复制图像数据,并以此为基础使用CreateDIBSection获得一个位图句柄
PBYTE pBitmapBits;
m_hBitmap = CreateDIBSection(hDC, pBitmapInfo, DIB_RGB_COLORS,
(PVOID*)&pBitmapBits, NULL, 0);
if ((m_hBitmap != NULL) && (pBitmapBits != NULL))
{
const PBYTE pTempBits = pBitmapImage + pBitmapInfo->bmiHeader.biSize +
pBitmapInfo->bmiHeader.biClrUsed * sizeof(RGBQUAD);
CopyMemory(pBitmapBits, pTempBits, pBitmapInfo->bmiHeader.biSizeImage);
// 解锁并释放位图资源
UnlockResource(hMemBitmap);
FreeResource(hMemBitmap);
return TRUE;
}
// 在发生错误时执行一些清理工作
UnlockResource(hMemBitmap);
FreeResource(hMemBitmap);
Free();
return FALSE;
}
//创建纯色的空白位图并将其创建为一个GDI 对象
BOOL Bitmap::Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor)
{
// 创建纯色的位图
m_hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);
if (m_hBitmap == NULL)
return FALSE;
// 设置宽度和高度
m_iWidth = iWidth;
m_iHeight = iHeight;
// 创建一个兼容的设备环境用以包含要绘制的位图
HDC hMemDC = CreateCompatibleDC(hDC);
// 创建一个指定颜色的纯白画刷用以填充位图
HBRUSH hBrush = CreateSolidBrush(crColor);
//将位图选入设备环境
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
// 用纯色画刷填充位图
RECT rcBitmap = { 0, 0, m_iWidth, m_iHeight };
FillRect(hMemDC, &rcBitmap, hBrush);
// 清理图形对象
SelectObject(hMemDC, hOldBitmap);
DeleteDC(hMemDC);
DeleteObject(hBrush);
return TRUE;
}
//绘制位图
void Bitmap::Draw(HDC hDC, int x, int y)
{
//确保位图句柄有效
if (m_hBitmap != NULL)
{
// 创建一个兼容的设备环境来临时存储位图
HDC hMemDC = CreateCompatibleDC(hDC);
// 将位图选入设备环境中
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, m_hBitmap);
// 将源设备环境的一个图像绘制到目的设备环境上的指定位置
BitBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0, SRCCOPY);
// 清理临时设备环境
SelectObject(hMemDC, hOldBitmap);
DeleteDC(hMemDC);
}
}
Bitmap 类 代码解析
类定义
Win32 定义了几个与位图有关的数据结构,因此我们在Bitmap 类代码中经常会看到几个不同的数据结构,其中一个结构是BITMAPINFOHEADER,它存储了与位图有关的头部信息。在从文件或资源中读取位图时,就将其头部存储在一个BITMAPINFOHEADER 结构中。
需要注意的是,我们的Bitmap 类只是设计用来加载BMP文件格式的24位未压缩位图图像。
构造函数和析构函数
所有Bitmap( )构造函数都非常简单,只是调用相应的Create( )函数,执行根据一个文件、一个资源或一种纯色来创建位图的具体工作。
构造函数的思路是,直接调用Create( )方法,利用数据加载Bitmap对象。
析构函数调用Free( )方法,释放与位图有关的内存并清除位图句柄。
Free( )方法首先查看位图句柄m_hBitmap是否有效,在这种情况下,它删除GDI 位图图像并清除句柄。这是释放与位图有关的内存所需的全部操作。
创建位图的三种方法
第一个Create( )方法负责从一个文件中加载位图。
这个方法首先调用Free( ),以确保清除了以前的任何位图数据,这适用于对不同的位图重复使用同一个Bitmap 对象的情况。然后打开文件,检查得到的文件句柄以确保顺利打开了文件而没有出现错误。然后从文件中读取位图的文件头,执行一些检查以确保正确读取而没有出现错误。文件头包含位图文件本身的信息,而信息头部包含位图的信息。接下来读取信息头部,并执行另一个错误检查。
从文件中正确读取头部之后,Create( )方法就可以开始读取位图图像数据的实际工作了。需要说明的是CreateDIBSection( ) Win32函数的用法。
CreateDIBSection()
功能:从原始位图数据中获得一个GDI 位图对象的句柄。
函数原型:
HBITMAP CreateDIBSection(HDC hdc,CONST BITMAPINFO *pbmi,UINT iUsage,VOID** ppvBits,HANDLE hSection,DWORD dwOffset);
函数参数:
hdc:设备环境句柄。如果iUsage的值是DIB_PAL_COLORS,那么函数使用该设备环境的逻辑调色板对与设备无关位图的颜色进行初始化。
pbmi:指向BITMAPINFO结构的指针,该结构指定了与设备无关位图的各种属性,其中包括位图的维数和颜色。
iUsage:指定由pbmi参数指定的BITMAPINFO结构中的成员bmiColors数组包含的数据类型(要么是逻辑调色板索引值,要么是原文的RGB值)。下列值是系统定义的,其含义为:
DIB_PAL_COLORS:表示成员bmiColors是hdc指定的设备环境的逻辑调色板,使用的是16位索引值数组。
DIB_RGB_COLORS:表示结构BITMAPINFO中包含了RGB值的数组。
ppvBits:指向一个变量的指针,该变量接收一个指向DIB位数据值的指针。
hSection:文件映射对象的句柄。函数将使用该对象来创建DIB(与设备无关位图)。该参数可以是NULL。
如果hSection不是NULL,那么它一定是文件映射对象的句柄。该对象是通过调用带有PAGE_READWRITE或PAGE_WRITECOPY标志的CreateFileMapping函数创建的。不支持只读的DIB类型。通过其他方法创建的句柄将会引起CreateDIBSection函数执行失败。
如果hSection不是NULL,那么函数CreateDIBSection将在hSection引用的文件映射对象中偏移量为dwOffset处获取位图的位数据值。应用程序可以在以后通过调用GetObject函数来检索hSection句柄,其中GetObject函数使用了GreateDIBSection函数返回的GBITMAP类型的对象。
如果hSection为NULL,那么系统将为与设备无关位图(DIB)分配内存。在这种情况下,函数CreateDIBSection将忽略参数dwOffset,应用程序无法在以后获取指向内存的句柄。通过调用GetObject函数来填充的DIBSECTION结构成员dshSection也将成为NULL。
DwOffset:指定从hSection引用的文件映射对象开始处算起的偏移量,超过这个偏移量的地方就是位图的位数据值开始存放的地方。在hSection为NULL时忽略该值。位图的位数据值是以DWORD为单位计算的。
返回值:如果函数执行成功,那么返回值是一个指向刚刚创建的与设备无关位图的句柄,并且*ppvBits指向该位图的位数据值;如果函数执行失败,那么返回值为NULL,并且*ppvBit也为NULL,若想获得更多错误信息,请调用GetLastError函数。
第二个Create( )方法用来从一个资源中加载位图
这个方法大致遵循与第一个Create( )方法相同的模式,只是在这里,位图信息时从内存中的一个资源中检索的,而不是从文件中读取的。
第一步找到位图资源,将资源加载到内存中,然后锁定资源,以便访问其原始数据。接下来存储位图的宽度和高度,复制图像数据,并以此为基础使用CreateDIBSection( )获得一个位图句柄。最后,在发生错误时执行一些清理工作。
最后一个Create( )方法创建纯色的空白位图。
这个方法与另外两个Create( )方法有很大的不同,因为它不涉及任何现有的位图数据,而使用一个名为CreateCompatibleBitmap( )的Win32 函数,根据提供的设备环境创建一个全新的位图。
这个Create( ) 方法中的大多数工作都涉及使用一种纯色来填充位图,首先创建一个兼容的设备环境以包含要绘制的位图,然后创建一个指定颜色的纯色画刷。接下来,将位图选入设备环境并使用纯色画刷进行填充。之后清理图形对象。
绘制位图
Draw( )方法也涉及绘制位图,因此它实际上与第三个Create( )方法相似。不过在这里,是将位图绘制到一个外部的设备环境中,而不是在位图自身上进行绘制。
Draw( )方法接受一个设备环境和一个xy坐标作为参数。绘制位图的第一步是确保位图句柄有效。如果句柄检查正常,则创建一个兼容的设备环境来临时存储位图,将位图选入设备环境。因为绘制图像总是从一个设备环境到另一个设备环境。
位图实际上是使用Win32 BitBlt( )函数绘制的,这个函数将源设备环境中的一个图像绘制到目标设备环境上的指定位置。
绘制位图图像有时候称为 位块传输( blitting ),在绘制图像时就是在块传输图像位。
开发Slideshow 幻灯片 示例
Slideshow 目录结构和效果图
Slideshow 目录结构:
Slideshow 效果图:
Slideshow 源代码
Resource.h
//-----------------------------------------------------------------
// Slideshow Resource Identifiers
// C++ Header - Resource.h
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// 图标 Range : 1000 - 1999
//-----------------------------------------------------------------
#define IDI_SLIDESHOW 1000
#define IDI_SLIDESHOW_SM 1001
//-----------------------------------------------------------------
// 位图 Range : 2000 - 2999
//-----------------------------------------------------------------
#define IDB_IMAGE1 2000
#define IDB_IMAGE2 2001
#define IDB_IMAGE3 2002
#define IDB_IMAGE4 2003
#define IDB_IMAGE5 2004
Slideshow.h
#pragma once
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include <windows.h>
#include "Resource.h"
#include "GameEngine.h"
#include "Bitmap.h"
//-----------------------------------------------------------------
// 全局变量
//-----------------------------------------------------------------
HINSTANCE g_hInstance; //程序实例句柄
GameEngine* g_pGame; //游戏引擎指针
const int g_iNUMSLIDES = 6; //更改幻灯片的张数
Bitmap* g_pSlides[g_iNUMSLIDES]; //存储Bitmap对象的指针
int g_iCurSlide; //当前幻灯片在数组中的索引
Slideshow.cpp
//-----------------------------------------------------------------
// 包含的文件
//-----------------------------------------------------------------
#include "Slideshow.h"
//-----------------------------------------------------------------
// 游戏事件函数
//-----------------------------------------------------------------
//初始化游戏
BOOL GameInitialize(HINSTANCE hInstance)
{
// 创建游戏引擎
g_pGame = new GameEngine(hInstance, TEXT("Slideshow"),
TEXT("Slideshow"), IDI_SLIDESHOW, IDI_SLIDESHOW_SM);
if (g_pGame == NULL)
return FALSE;
// 设置帧率
g_pGame->SetFrameRate(1);
// 设置实例句柄
g_hInstance = hInstance;
return TRUE;
}
//开始游戏
void GameStart(HWND hWindow)
{
// 创建并加载幻灯片位图 这段代码阐明创建位图的三种方法
HDC hDC = GetDC(hWindow);
g_pSlides[0] = new Bitmap(hDC, TEXT("Image1.bmp"));
g_pSlides[1] = new Bitmap(hDC, TEXT("Image2.bmp"));
g_pSlides[2] = new Bitmap(hDC, TEXT("Image3.bmp"));
g_pSlides[3] = new Bitmap(hDC, IDB_IMAGE4, g_hInstance);
g_pSlides[4] = new Bitmap(hDC, IDB_IMAGE5, g_hInstance);
g_pSlides[5] = new Bitmap(hDC, 640, 480, RGB(0, 0, 255));
// 设置第一个幻灯片
g_iCurSlide = 0;
}
//游戏结束
void GameEnd()
{
// 清理幻灯片位图
for (int i = 0; i < g_iNUMSLIDES; i++)
delete g_pSlides[i];
// 清除游戏引擎
delete g_pGame;
}
void GameActivate(HWND hWindow)
{
}
void GameDeactivate(HWND hWindow)
{
}
//绘制当前的幻灯片位图
void GamePaint(HDC hDC)
{
// 绘制当前幻灯片位图
g_pSlides[g_iCurSlide]->Draw(hDC, 0, 0);
}
//游戏循环 逐个显示幻灯片,几秒一个
void GameCycle()
{
static int iDelay = 0;
// 设置转到下一个幻灯片之前的3秒延迟
if (++iDelay > 3)
{
// 恢复延迟计数器
iDelay = 0;
// 显示下一张幻灯片
if (++g_iCurSlide == g_iNUMSLIDES)
g_iCurSlide = 0;
// 强制重新绘制,以便绘制下一个幻灯片
InvalidateRect(g_pGame->GetWindow(), NULL, FALSE);
}
}
Slideshow 源代码解析
GameCycle( ) 函数
GameCycle( )函数负责在一次短暂的停顿之后移动到下一张幻灯片。
因为游戏引擎限制了最小帧频为1帧/秒,所以必须在GameCycle( )函数中进一步实现延迟,从而放慢幻灯片的放映。
对于观看每一张幻灯片来说,3秒钟是一个合理的延迟,因此GameCycle( )函数使用了一个静态变量iDelay来延迟各张幻灯片,在进入下一张幻灯片之前等待3秒钟。能够这样做是因为帧频设置为每秒1个周期,可以在第3个周期(即每3秒)才更新幻灯片。
为了移动到下一张幻灯片,需要增加iCurSlide。如果增加iCorSlide时超过了最后一张幻灯片,将从第一张幻灯片重新开始放映幻灯片。