EGE专栏:EGE专栏
上一篇:EGE基础入门篇(七):组合图形
下一篇:EGE基础入门篇(九):双缓冲与手动渲染
一、清屏
1. 控制台清屏
当控制台输出的字符内容达到一定行数的时候,控制台会自动向下滚动,以便用户能查看到最新的输出。并且用户可拖动滚动条查看之前输出的信息,便于查看历史记录。
但如果是用控制台做UI界面的话,太多的信息显示在窗口中反而显得杂乱,并且很难一眼看到关键信息。如下图所示:
如果在输出前,先调用cls命令清屏,将之前的输出信息清除,再输出当前要显示的文字,那么窗口上只显示相关内容,清晰明了,有利于用户的阅读和界面交互。如下图所示:
但是清屏会使得控制台之前的输出信息会丢失,所以清屏并在输出调试信息时使用。
控制台可以控制光标位置,当需要动态输出时,比如加载进度的显示,可以稍微跳转到目标位置,改变字符,这适用于明确输出内容并且只有少量修改的情况。在不清楚之前控制台中的输出内容,以及需要大量改变输出的时候,还是需要进行清屏,然后重新打印输出。
1.1 控制台清屏命令:cls
在程序中包含 <stdlib.h> 头文件:
#include <stdlib.h>
当需要对控制台清屏时,调用 system("cls")
即可。
控制台清屏示例:
下面是纯控制台的一个程序,在输出N行信息后,通过调用 system("cls")
将控制台清屏。
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
int main()
{
for (int i = 0; i < 30; i++) {
printf("[%2d]输出信息\n", i);
}
printf("\n按任意键将清除所有输出...\n");
getch();
system("cls"); //清屏
printf("清除完毕,按任意键退出");
getch();
return 0;
}
2. 图形窗口清屏
2.1 像素颜色的表示
一个像素的颜色可以用不同的位数来表示,如果只用一位(1 Bit)来表示,那么颜色值只有0和1,通常0表示黑,而1表示白,也可以根据实际,设置不同的颜色。
现在的显示器基本都用RGB来表示颜色,每一个像素点由红绿蓝三原色的子像素点组成,用来三原色中的每种颜色亮度的位数称为位深度。显示器的位深度通常为6位,8位或10位,可以由显卡驱动在支持的最大位深度和较小位深度之间调节。大多数显示器支持的最大位深度位为8位,达到8位后大多数人已难以分辨,
如果位深度为6,那么每种原色可以有
2
6
=
64
2^{6} =64
26=64种亮度,三种原色组合则一共可以表示
6
4
3
=
262144
64^{3}=262144
643=262144,约26万种颜色。
如果位深度为8,那么每种原色可以有
2
8
=
256
2^{8} =256
28=256种亮度,三种原色组合则一共可以表示
25
6
3
=
16777216
256^{3}=16777216
2563=16777216,约1678万种颜色。
在EGE中,像素颜色是ARGB格式,用4个字节来表示。每种原色用一个字节,可以表示256种亮度,一共可以表示16777216种颜色。当然,最终能够显示的颜色数,还是要看显示器能否支持。
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
N⋅width⋅height 字节的存储空间来表示窗口区域的图像内容。这个存储区域称之为帧缓冲(Frame Buffer),窗口的帧缓冲通常是在开辟在内存中,帧缓冲数据最终需要传输到显存中,由显卡读取并显示到屏幕上。
在EGE中,像素颜色格式为ARGB格式,每一个像素点的颜色都用4个字节来表示。
2.2 窗口清屏的含义
屏幕中每一个像素都需要有一个颜色值来与之对应,如果RGB颜色值为0,那么像素颜色对应的是纯黑。
正因为每个像素都有一个颜色值与之对应,所以清屏时,不是将帧缓冲区删除,而是对帧缓冲里的每一个元素进行赋值,使整个帧缓冲里的每一个元素都是同一个值(可以是任意的颜色值)
。这样窗口里显示的就都是同一个颜色了,从而完成对图形的清除。
2.3 cleardevice()函数
EGE中的 cleardevice() 函数可以将窗口帧缓冲数据统一设置成同一个颜色值,即背景色。背景色可以由 setbkcolor() 或 setbkcolor_f() 进行设置。
setbkcolor_f(WHITE);
cleardevice();
清屏示例:
#include <graphics.h>
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;
}
2.4 全部清屏和部分清屏
全部清屏是将整个窗口的帧缓冲数据都赋同一个颜色值,这个操作实际上和绘制一个不透明的填充矩形是差不多的。cleardevice() 函数的功能便是全部清屏。
当仅有一小部分需要修改的时候,我们并不需要将整个窗口清屏,只对部分区域清屏能提高绘图效率。因此当清楚绘图内容时,如果只需要修改一小部分内容,可以只对某一部分区域进行清屏,而不需要全部清屏。此时可以用填充矩形 bar() 将某一矩形区域填充成背景色,或者直接对帧缓冲进行赋值等均可。
部分清屏用于绘图复杂时,能节省绘图时间,提高帧率。如果只是简单地绘图,全部清屏和部分清屏差别不大。
2.5 绘制新图形是否一定需要清屏
清屏只是为了消除之前的绘图痕迹,保证后面图形能够正常绘制。如果直接绘图能够完全将之前的绘图痕迹覆盖,那就不是必要的。但为了稳妥起见,绘图前还是需要清屏的,这避免了许多意想不到的情况发生。
现在的计算机性能很高,清屏并不是主要耗时原因。如果是比较低性能的嵌入式芯片,那就需要根据实际情况斟酌一番,性能差时
能省则省。
二、重绘
1. 为何需要清屏重绘
如下图所示,中间小球在其它图形后面,部分被遮挡。现在蓝色小球要从左边移动到右边,该如何处理才能达到所要求的效果呢? 这里有两个要求:消除之前绘制小球时留下的痕迹,新绘制的小球需要保持原来的深度,即遮挡关系与原来一致。
清屏重绘是一种简单而直接的处理方式。先将外围矩形区域内的所有图形数据清除,再按原来的绘制顺序进行绘制即可,改变的也仅仅是小球时绘制时的位置。
如何想要不清屏而单纯地改变对应位置的像素,那是十分复杂的。首先需要对所有图形进行深度排序,消除痕迹需要了解绘图之后是否被其它图形遮挡,遮挡了哪些像素,并且需要存储图形的每个像素在绘图之前的值,如果图形都是透明的,那基本是要按照图形顺序重新算一遍。而在新位置绘制时,又要考虑图形之前的遮挡,当图形有透明度时,那必须按照原顺序重新计算,十分复杂。
2. 图形的重绘
如果程序的图形界面是动态的,需要保证程序能有对图形进行重绘的能力,不管区域中原来绘制有什么图形,都能够绘制出目标图形,这通常需要先进行清屏,将在区域内的图形全部清空,再重新绘制。
#include <graphics.h>
//圆
typedef struct Circle
{
float x, y; //圆心坐标
float radius; //半径
} Circle;
int main()
{
timeBeginPeriod(2);
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 = 4.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(2);
closegraph();
return 0;
}
EGE专栏:EGE专栏
上一篇:EGE基础入门篇(七):组合图形
下一篇:EGE基础入门篇(九):双缓冲与手动渲染