显示图片

在屏幕上显示图片

现在你已经打开了一个窗口,让我们在上面放一个图像。
注意:从现在开始,教程将仅涵盖源代码的关键部分。对于完整的程序,您将必须下载完整的源代码。

//初始化
bool init();

//载入媒体资源
bool loadMedia();

//释放资源,关闭SDL
void close();

在第一个教程中,我们将所有内容都放在 main 函数中。由于这是一个小程序,但是在实际程序(如视频游戏)中,您希望代码尽可能模块化。这意味着您希望代码位于整洁的块中,每个块都易于调试和重用。这里意味着我们有处理初始化、加载媒体和关闭 SDL 应用程序的功能。我们在源文件顶部附近声明这些内容。
我收到很多关于如何将此函数称为"close"的电子邮件,因为不支持函数重载,因此在C中会导致冲突。这是我在本教程中使用C++的原因之一。因此,这不是一个错误,这是函数被称为"close"。

//定义窗口
SDL_Window* gWindow = NULL;
	
//定义窗口所包含的表层
SDL_Surface* gScreenSurface = NULL;

//定义载入图片用的表层
SDL_Surface* gHelloWorld = NULL;

在这里,我们声明一些全局变量。通常,您希望避免在大型程序中使用全局变量。我们在这里这样做的原因是,我们希望源代码尽可能简单,但在大型项目中,全局变量使事情变得更加复杂。由于这是一个单一的源文件程序,我们不必太担心它。
下面是一种称为 SDL Surface 的新数据类型。SDL 图面只是一种图像数据类型,其中包含图像的像素以及渲染图像所需的所有数据。SDL 表面使用软件渲染,这意味着它使用 CPU 进行渲染。可以渲染硬件图像,但难度更大,因此我们将首先以简单的方法学习它。在以后的教程中,我们将介绍如何渲染 GPU 加速图像。
我们将在这里处理的图像是屏幕图像(您在窗口内看到的图像)和我们将从文件加载的图像。
请注意,这些是指向 SDL 图面的指针。原因是
1) 我们将动态分配内存以加载图像
2) 最好按内存位置引用图像。想象一下,你有一个砖墙的游戏,它由多次渲染的同一砖块图像组成(如超级马里奥兄弟)。在内存中拥有图像的数十个副本是浪费的,因为您可以拥有图像的一个副本并一遍又一遍地渲染它。
另外,请始终记住初始化指针。在声明它们时,我们立即将它们设置为 NULL。

//初始化函数的实现
bool init()
{
	//初始化标识
	bool success = true;

	//初始化SDL
	if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
	{
		printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() );
		success = false;
	}
	else
	{
		//创建窗口
		gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
		if( gWindow == NULL )
		{
			printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
			success = false;
		}
		else
		{
			//获取窗口的表层
			gScreenSurface = SDL_GetWindowSurface( gWindow );
		}
	}

	return success;
}

我们不能直接在窗口上画画,但可以在表层上画画。为了在窗口上画画,我们需要用SDL_GetWindowSurface()获取到窗口的表层,在表层上画完后再进行渲染。

//载入媒体资源
bool loadMedia()
{
	//定义成功标识
	bool success = true;

	//载入位图
	gHelloWorld = SDL_LoadBMP( "hello_world.bmp" );
	if( gHelloWorld == NULL )
	{
		printf( "Unable to load image %s! SDL Error: %s\n", "02_getting_an_image_on_the_screen/hello_world.bmp", SDL_GetError() );
		success = false;
	}

	return success;
}

在加载媒体函数中,我们使用SDL_LoadBMP加载图像。SDL_LoadBMP采用 bmp 文件的路径并返回加载的曲面。如果该函数返回 NULL,则表示它失败,因此我们使用SDL_GetError向控制台打印错误。
需要注意的一件重要事情是,这段代码假定您有一个名为"02_getting_an_image_on_the_screen"的目录,该目录在工作目录中包含一个名为"hello_world.bmp"的图像。工作目录是应用程序认为它正在运行的位置。通常,工作目录是可执行文件所在的目录,但某些程序(如 Visual Studio)会将工作目录更改为 vcxproj 文件所在的位置。因此,如果应用程序找不到图像,请确保它位于正确的位置。
同样,如果程序正在运行,但它无法加载映像,则可能是存在工作目录问题。工作目录在操作系统和IDE与IDE之间的功能不同。如果谷歌搜索如何查找或修复工作目录无法找到解决方案,我建议在带有"hello_world.bmp"的"02_getting_an_image_on_the_screen"文件夹中移动,直到程序最终可以加载它。

void close()
{
	//释放表层
	SDL_FreeSurface( gHelloWorld );
	gHelloWorld = NULL;

	//销毁窗口
	SDL_DestroyWindow( gWindow );
	gWindow = NULL;

	//退出SDL子系统
	SDL_Quit();
}

在我们的清理代码中,我们像以前一样销毁窗口并退出 SDL,但我们还必须处理加载的表面。我们通过用SDL_FreeSurface释放它来做到这一点。不要担心界面表面,SDL_DestroyWindow会处理它。
确保养成这样的习惯,即当指针不指向任何内容时,指针指向NULL。

int main( int argc, char* args[] )
{
	//初始化
	if( !init() )
	{
		printf( "Failed to initialize!\n" );
	}
	else
	{
		//载入媒体
		if( !loadMedia() )
		{
			printf( "Failed to load media!\n" );
		}
		else
		{
			//使用图片
			SDL_BlitSurface( gHelloWorld, NULL, gScreenSurface, NULL );
			
			//更新表层
			SDL_UpdateWindowSurface( gWindow );

			//延迟2秒
			SDL_Delay( 2000 );
		}
	}

	//释放内存,退出SDL
	close();

	return 0;
}

在我们的 main 函数中,我们初始化 SDL 并加载映像。如果成功,我们使用SDL_BlitSurface将加载的图片覆盖到屏幕表面上。这个模块的作用是获取源曲面,并将其副本印在目标曲面上。SDL_BlitSurface的第一个参数是源图像。第三个参数是目的地。我们将在以后的教程中讨论第 2 个和第 4 个参数。
现在,如果这是我们唯一的绘图代码,我们仍然看不到我们在屏幕上加载的图像。还有一个步骤。

在屏幕上绘制要为此帧显示的所有内容后,我们必须使用SDL_UpdateWindowSurface更新屏幕。当您绘制到屏幕时,您通常不会绘制到屏幕上的图像。默认情况下,大多数渲染系统都是双缓冲的。这两个缓冲区是前缓冲区和后缓冲区。
当您进行绘制调用(如SDL_BlitSurface)时,将呈现到后台缓冲区。您在屏幕上看到的是前面的缓冲区。我们这样做的原因是,大多数帧都需要将多个对象绘制到屏幕上。如果我们只有一个前缓冲区,那么太太慢了,帧率太低。因此,我们要做的是首先将所有内容绘制到后缓冲区,一旦完成,我们交换了后置和前置缓冲区,以便现在用户可以看到完成的帧。
这也意味着您不会在每次点亮后调用SDL_UpdateWindowSurface,只有在当前帧的所有图案都绘制完成后才调用。

posted @ 2022-01-07 11:10  LiXintao  阅读(158)  评论(0)    收藏  举报