EGE基础入门篇(八):清屏与重绘

EGE专栏:EGE专栏

上一篇:EGE基础入门篇(七):组合图形

下一篇:EGE基础入门篇(九):双缓冲与手动渲染



一、清屏

  清屏清除屏幕(clear screen) ,可以将窗口上的输出内容全部清空,是重绘常用的一种方法。

1. 控制台清屏

  在控制台下,执行 cls命令 将清除控制台中的所有输入输出,光标回到左上角。

  当控制台输出的字符内容达到一定行数的时候,控制台会自动向下滚动,以便用户能查看到最新的输出。并且用户可以拖动滚动条查看之前输出的信息,便于查看历史记录
  但如果是用控制台做UI界面的话,太多的信息显示在窗口中反而显得杂乱,并且很难一眼看到关键信息。如下图所示:

EGE基础入门篇(八):清屏与重绘_第1张图片

  如果在输出前,先调用cls命令清屏,将之前的输出信息清除,再输出当前要显示的文字,那么窗口上只显示相关内容,清晰明了,有利于用户的阅读和界面交互。如下图所示:

EGE基础入门篇(八):清屏与重绘_第2张图片
  但是清屏会使得控制台之前的输出信息会丢失,所以清屏并不适合在输出调试信息时使用。

  控制台可以控制光标位置,当需要动态输出时,比如加载进度的显示,可以稍微跳转到目标位置,改变字符,这适用于明确输出内容并且只有少量修改的情况。在不清楚之前控制台中的输出内容,以及需要改变大量字符的时候,还是需要进行清屏,然后重新打印输出。

1.1 控制台清屏命令:cls

控制台的 cls命令 可以通过 system() 函数调用, system() 函数在 头文件中声明。

  在cpp源文件中包含 头文件:

#include 

  当需要对控制台清屏时,调用 system("cls") 即可。

system("cls");

控制台清屏示例:
  下面是纯控制台的一个程序,在输出N行信息后,通过调用 system("cls") 将控制台清屏。

#include 
#include 
#include 

int main()
{
	for (int i = 0; i < 30; i++) {
		printf("[%2d]输出信息\n", i);
	}
	
	printf("\n按任意键将清除所有输出...\n");
	getch();
	
	//清屏
	system("cls");
	
	printf("清除完毕,按任意键退出");

	getch();
	return 0;
}

EGE基础入门篇(八):清屏与重绘_第3张图片



2. 图形窗口清屏

  控制台可以将输出存储到一个字符序列里,输出字符串时就直接把字符串添加进字符序列中。那窗口显示的内容是怎么存储的呢?

  光栅显示器能显示图像是依靠显示器上一个个会发光的像素,这些像素通过发出不同的色光来组成一张彩色的图像。为了让窗口显示出对应的图像,我们就需要把组成图像的每一个像素的颜色值都存储下来。等到系统来读取我们存储的颜色数据时,系统就能按照这些颜色数据,一一改变对应像素的颜色,这样我们就能在屏幕上看到我们想要的图像。
EGE基础入门篇(八):清屏与重绘_第4张图片

2.1 窗口像素颜色值的存储:帧缓冲

  窗口区域通常是矩形,对于宽高分别为 w i d t h width width, h e i g h t height height 的矩形区域,如果表示一个像素颜色值需要 N N N个字节,那么开辟一个大小为 N ⋅ w i d t h ⋅ h e i g h t N \cdot width \cdot height Nwidthheight 字节的存储空间就可以用来表示窗口区域的图像内容。这个存储空间称之为帧缓冲(Frame Buffer),窗口的帧缓冲通常是在开辟在内存中,帧缓冲数据最终需要传输到显存中,由显卡读取并显示到屏幕上。
  在EGE中,像素颜色格式为ARGB格式,每一个像素点的颜色都用4个字节来表示,这样可以表示约1678万种颜色。

EGE基础入门篇(八):清屏与重绘_第5张图片

2.1.1 获取帧缓冲首地址

  EGE中图像和窗口内容的帧缓冲首地址,可以使用 getbuffer() 函数获取。
  像素颜色的类型为color_t,帧缓冲首地址类型是color_t *

color_t* buffer = getbuffer((PIMAGE)NULL);

  得到帧缓冲首地址后,我们便可以直接读取和修改帧缓冲中的值。

2.1.2 获取帧缓冲大小

  帧缓冲区保存的颜色值数量和矩形区域中的像素数量一致,假设矩形区域宽高分别为 w i d t h \mathrm{width} width h e i g h t \mathrm{height} height 个像素,那么缓冲区所保存的颜色数量为 w i d t h × h e i g h t \mathrm{width} \times \mathrm{height} width×height
  窗口和图像的宽高可以通过 getwidth()getheight() 获取。

int width = getwidth();
int height = getheight();

2.1.3 通过读写帧缓冲数据修改像素颜色

  帧缓冲是块连续的内存区域,我们得到的仅仅是它的首地址,相当于是要访问一个一维数组,所以还需要知道坐标为 ( x , y ) (x,y) (x,y) 的像素在帧缓冲中的索引。
  图像中的像素颜值值会按行连续存储在帧缓冲中,而坐标 ( x , y ) (x,y) (x,y) 的像素位置在第 y y y 行第 x x x 列,所以对应的索引为 y × w i d t h + x y \times \mathrm{width} + x y×width+x

//设置坐标为 (x, y)的像素颜色
buffer[y * width  + x] = color;

EGE基础入门篇(八):清屏与重绘_第6张图片

示例代码

//获取帧缓冲首地址
color_t* frameBuffer = getbuffer((PIMAGE)NULL);
//获取帧缓冲的大小
int width = getwidth(), height = getheight();

//将坐标为(x, y)的像素颜色值设置成白色
int x = 100, y = 200;
frameBuffer[x + y * width] = WHITE;

//获取坐标为(x, y)的像素颜色值
color_t pixelColor = frameBuffer[x + y * width]

  如上所示便能直接通过帧缓冲首地址来进行绘图或读取像素数据。
  如果想这样绘图,有一个事项需要特别注意,那就是EGE会自动检测有没有进行绘图操作,以免进行一些不必要的数据传输操作。如果没有调用EGE的绘图函数,是检测不到绘图的,所以很可能就不会显示出来。
  所以如果想要通过帧缓冲首地址来修改像素数据,需要加一些特别的操作来让图像显示,如调用 delay_fps() 等函数。

#include 
#include 

double clamp(double value, double min, double max)
{
	return (value < min) ? min : ((value > max) ? max : value);
}

int main()
{
	initgraph(640, 640, INIT_RENDERMANUAL);

	//获取帧缓冲首地址
	color_t* frameBuffer = getbuffer((PIMAGE)NULL);
	int width = getwidth((PIMAGE)NULL);
	int height = getheight((PIMAGE)NULL);

	float xCenter = width / 2.0f, yCenter = height / 2.0f;
	color_t bottomColor = EGERGB(0, 180, 255);
	color_t topColor = EGERGB(230, 230, 0);

	//通过帧缓冲来进行绘图
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			//计算到中心距离的平方
			double distanceSquare = (x - xCenter) * (x - xCenter) + (y - yCenter) * (y - yCenter) ;
			double t = (cos(distanceSquare/ (60 * 60)) + 1.0) / 2.0;

			t = clamp(t, 0.0, 1.0);

			//计算当前像素颜色
			unsigned char r = round((1 - t) * EGEGET_R(bottomColor) + t * EGEGET_R(topColor));
			unsigned char g = round((1 - t) * EGEGET_G(bottomColor) + t * EGEGET_G(topColor));
			unsigned char b = round((1 - t) * EGEGET_B(bottomColor) + t * EGEGET_B(topColor));

			color_t color = EGERGB(r, g, b);

			//修改帧缓冲像素颜色数据
			frameBuffer[x + y * width] = color;
		}
	}

	//调用delay_fps()函数,让图像显示
	delay_fps(60);

	getch();

	closegraph();

	return 0;
}

2.2 窗口清屏的含义

  屏幕中每一个像素都需要有一个颜色值来与之对应,如果RGB颜色值为0,那么像素颜色对应的是纯黑。
  正因为每个像素都有一个颜色值与之对应,所以清屏时,不是将帧缓冲区删除,而是对帧缓冲里的每一个元素进行赋值,使整个帧缓冲里的每一个元素都是同一个值(可以是任意的颜色值) 。这样显示到屏幕上时,窗口里的像素只显示同一个颜色,从而完成对图形的清除。
EGE基础入门篇(八):清屏与重绘_第7张图片

2.3 cleardevice()函数

  EGE中的 cleardevice() 函数可以将窗口帧缓冲数据统一设置成同一个颜色值,即背景色。背景色可以由 setbkcolor()setbkcolor_f() 进行设置。

setbkcolor_f(WHITE);
cleardevice();

清屏示例:

#include 

int main()
{
	initgraph(640, 480, INIT_RENDERMANUAL);

	//设置背景色并修改背景
	setbkcolor(WHITE);						
	
	setfillcolor(EGERGB(0, 163, 254));
	bar(40, 40, 600, 440);

	//暂停,按任意键继续
	getch();

	//清屏
	cleardevice();

	getch();

	setfillcolor(EGERGB(249, 186, 0));
	bar(80, 80, 560, 400);

	//暂停,按任意键继续
	getch();

	closegraph();

	return 0;
}

EGE基础入门篇(八):清屏与重绘_第8张图片

2.4 全部清屏和部分清屏

  清屏实际上就是将帧缓冲中某个区域里的值都赋同一个颜色值,这些区域通常都是矩形区域,因为窗口和屏幕基本都是矩形,并且构成矩形的是四条和坐标轴平行的直线,比较容易计算。

  全部清屏是将整个窗口的帧缓冲数据都赋同一个颜色值,这个操作实际上和绘制一个和窗口同样大小的不透明填充矩形是差不多的。cleardevice() 函数的功能便是全部清屏。

  当仅有一小部分需要修改的时候,我们并不需要将整个窗口清屏,只对部分区域清屏能提高绘图效率。因此当清楚绘图内容时,如果只需要修改一小部分内容,可以只对某一部分区域进行清屏,而不需要全部清屏。此时可以用填充矩形 bar() 将某一矩形区域填充成背景色,或者直接对帧缓冲进行赋值等均可。
  当绘图复杂时,部分清屏能节省绘图时间,提高帧率。如果只是简单地绘图,全部清屏和部分清屏差别不大。

2.5 绘制新图形是否一定需要清屏

  清屏只是为了消除之前的绘图痕迹,保证后面图形能够正常绘制。如果直接绘图能够完全将之前的绘图痕迹覆盖,那清屏就不是必要的。但为了稳妥起见,绘图前还是需要清屏的,这避免了许多意想不到的情况发生。
  现在的计算机性能很高,清屏并不是主要耗时原因。如果是比较低性能的嵌入式芯片,那就需要根据实际情况斟酌一番,性能差时能省则省。

二、重绘

  重绘是指对图形重新进行绘制,一般是需要先进行清屏。

1. 为何需要清屏重绘

  当需要改变窗口中图形的颜色,位置,大小等参数时,就需要将对应像素的颜色值进行改变。不仅需要消除之前图形绘制的痕迹,还要考虑图形绘制的顺序。

  如下图所示,中间小球在其它图形后面,部分被遮挡。现在蓝色小球要从左边移动到右边,该如何处理才能达到所要求的效果呢? 这里有两个要求:消除之前绘制小球时留下的痕迹,新绘制的小球需要保持原来的深度,即图形间的遮挡关系与原来保持一致。

EGE基础入门篇(八):清屏与重绘_第9张图片

  清屏重绘是一种简单而直接的处理方式。先将外围矩形区域内的所有图形数据清除,再按原来的绘制顺序进行绘制即可,改变的也仅仅是小球时绘制时的位置。

  如何想要不清屏而单纯地改变对应位置的像素,那是十分复杂的。首先需要对所有图形进行深度排序,消除痕迹需要了解绘图之后是否被其它图形遮挡,哪些像素被遮挡还需要对后绘制的图形进行计算,并且需要存储图形的每个像素在绘图之前的值。如果图形都是透明的,那基本是要按照图形顺序重新算一遍。而在新位置绘制时,又要考虑图形之前的遮挡,当图形有透明度时,那必须按照原顺序重新计算,十分复杂。

2. 图形的重绘

  如果程序的图形界面是动态的,需要 保证程序能有对图形进行重绘的能力,不管区域中原来遗留下什么绘画痕迹,都能够绘制出目标图形来。这通常需要先进行清屏,将在区域内的图形全部清空,再重新绘制。

#include 

//圆
typedef struct Circle
{
	float x, y;		//圆心坐标
	float radius;	//半径
} Circle;

int main()
{
	timeBeginPeriod(1);
	int winWidth = 640, winHeight = 480;

	initgraph(winWidth, winHeight, INIT_RENDERMANUAL | INIT_NOFORCEEXIT);

	//设置背景色并修改背景
	setbkcolor(WHITE);	
	ege_enable_aa(true);

	int margin = 20;
	float rightBoundary = winWidth - margin, leftBoundary = margin;

	//移动速度(像素每帧)
	const float absSpeed = 2.0f;
	float speed = absSpeed;
	Circle cir = { margin + 100, winHeight / 2, 100 };
	
	color_t yellowColor = EGEARGB(255, 255, 217, 102);
	color_t pinkColor = EGEARGB(120, 193, 102, 89);
	color_t blueColor = EGEARGB(255, 60, 120, 216);

	setlinewidth(2);

	bool first = true;

	for (; is_run(); delay_fps(60)) {
		//清屏
		cleardevice();

		//绘制小球
		setfillcolor(blueColor);
		ege_fillellipse(cir.x - cir.radius, cir.y - cir.radius, 2 * cir.radius, 2 * cir.radius);

		//绘制顶部矩形
		setfillcolor(yellowColor);
		setcolor(BLACK);
		ege_fillrect(margin, winHeight / 2 - 100, winWidth - 2 * margin, 40);
		ege_rectangle(margin, winHeight / 2 - 100, winWidth - 2 * margin, 40);

		//绘制顶部矩形
		setfillcolor(pinkColor);
		setcolor(BLACK);
		ege_fillrect(margin, winHeight / 2 + 100 - 40, winWidth - 2 * margin, 40);
		ege_rectangle(margin, winHeight / 2 + 100 - 40, winWidth - 2 * margin, 40);

		//移动小球
		cir.x += speed;

		//边界碰撞判断
		if (cir.x + cir.radius > rightBoundary) {
			cir.x = rightBoundary - cir.radius;
			speed = -absSpeed;
		}
		else if (cir.x - cir.radius < leftBoundary) {
			cir.x = leftBoundary + cir.radius;
			speed = absSpeed;
		}
	}
	
	timeEndPeriod(1);

	closegraph();

	return 0;
}


EGE专栏:EGE专栏

上一篇:EGE基础入门篇(七):组合图形

下一篇:EGE基础入门篇(九):双缓冲与手动渲染

你可能感兴趣的:(EGE,c++,ege)