比如,可以基于 EasyX 图形库很快的用几何图形画一个屋子,或者一辆移动的小车,可以编写俄罗斯方块、饕餮蛇、黑白棋等小游戏,可以练习图形学的各种算法,等等。

C/C措辞运用EasyX绘图库实现简单纯真时钟

大略理解了EasyX后,就可以开干了。
本文开拓环境是windows 11和Visual Studio 2022。
首先打开vs2022并创建一个空的掌握台项目,在项目里新建main.cpp,#include EasyX的头文件graphics.h。
提示:由于EasyX是针对C++的绘图库,必须用cpp文件来开拓编译,但是编码可以是c也可以是c++。

EasyX利用非常大略,可以一边阅读EasyX在线文档(https://docs.easyx.cn/zh-cn/intro)一边开拓。
基本流程:

#include <graphics.h>// 引用图形库头文件int main(){initgraph(600, 400);// 创建绘图窗口,大小为 600x400 像素//绘图操作closegraph();// 关闭绘图窗口return 0;}

下面基于以上根本代码逐步添加。

准备事情

由于EasyX默认情形下天生的绘图界面是玄色的背景,我们可以利用setbkcolor函数变动界面背景色为白色:

setbkcolor(WHITE);cleardevice(); //设置背景色后要打消设备才能生效setlinecolor(BLACK);//设置线条颜色(白底黑线)

在 EasyX 中,坐标分两种:物理坐标和逻辑坐标。

物理坐标是描述设备的坐标体系。

坐标原点在设备的左上角,X 轴向右为正,Y 轴向下为正,度量单位是像素。

坐标原点、坐标轴方向、缩放比例都不能改变。

逻辑坐标是在程序中用于绘图的坐标体系。

坐标默认的原点在窗口的左上角,X 轴向右为正,Y 轴向下为正,度量单位是点。

默认情形下,逻辑坐标与物理坐标是逐一对应的,一个逻辑点即是一个物理像素。

默认原点(0,0)在绘图窗口的左上角,示例中为了方便绘制时钟,将原点修正为窗口的中央点:

//假设窗口大小为600x400,则中央点为(600/2,400/2)setorigin(300,200);//坐标原点设置成功后,(300,200)位置即为绘图坐标的原点(0,0)

为方便代码编写,先定义了几个常量:

#define W 800//窗口宽度#define H 620//窗口高度#define oY H/2//调度坐标原点y为窗口高度的一半#define R 290//时钟内圆半径#define PI 3.14#define HD PI/180 //因需多次打算弧度,提前算出部分公共值#define R_2 R/2绘制钟面内外框

时钟可以分成钟面外框、大小刻度和指针。
由于指针指向是须要随韶光动态变革,因此不能固定不变。
而钟面外框是固定不变的,可以只绘制一次。
(大小刻度也是固定不变的,但是由于与秒针行走路径有重叠,也要动态绘制)

示例中钟面外框绘制成两个大圆,都有一定的宽度。

绘制圆形的函数是void circle( int x, int y, int radius );参数分别是圆心的x、y坐标以及半径长度,如果要绘制宽度,可以利用setlinestyle()函数设置线条的形状和宽度:

setlinecolor(BLACK);//设置线条颜色setlinestyle(PS_SOLID, 10);//设置线条样式和宽度circle(0, 0, oY - 10); //绘制时钟外黑框//画出时钟的内框setlinestyle(PS_SOLID, 4);circle(0, 0, R);setlinestyle(PS_SOLID, 1);//绘制完钟面内外两个框后规复线条宽度为1

绘制钟面内外两个框

绘制大小刻度和12个数字

由于大小刻度与指针有部分重叠,因此也要跟随指针一起反复动态绘制。
因此都将它们放入一个while循环中。

示例中,大刻度画小实心圆,小刻度画短线条。
画刻度和指针都须要定位坐标,须要用上数学打算,须先引入头文件math.h。
详细的坐标打算方法可以自行度娘或看前一篇js+canvas绘制时钟,里面有大略的阐明。

setfillcolor(BLACK);//大刻度实心小圆的添补色(玄色)double _x, _y;TCHAR s[3];settextcolor(BLACK);LOGFONT f;gettextstyle(&f);f.lfHeight = 36; //设置字体大小_tcscpy_s(f.lfFaceName, _T("serif")); //设置字体名称f.lfQuality = ANTIALIASED_QUALITY;//设置字体平滑效果settextstyle(&f);for (int j = 9, i = 0; i < 12; j++, i++) {//由于坐标0度指向刻度3,以是有针对性地改动一下if (i > 2) { j = i - 3; }int _t = j 30;//每个大格30度,用于下面的弧度打算_x = cos(HD _t) (R - 5);_y = sin(HD _t) (R - 5);//打算大刻度的圆心坐标fillcircle(_x, _y, 5);//绘制大刻度//开始绘制笔墨,先打算笔墨显示位置的矩形坐标swprintf_s(s, _T("%d"), i == 0 ? 12 : i);RECT tr;//定义笔墨矩形构造if (j == 10) {//改动部分矩形形状,j==10指向1时tr.left = _x - 50;tr.top = _y;tr.right = _x;tr.bottom = _y + 50;}else if(j==4){//指向7时tr.left = _x +50;tr.top = _y;tr.right = _x;tr.bottom = _y -50;}else if (j == 1) {//指向4时tr.left = _x - 50;tr.top = _y - 50;tr.right = _x;tr.bottom = sin(HD (5+_t)) (R - 5);}else if( j == 7) {//指向10时tr.left = _x+10;tr.top = sin(HD (5 + _t)) (R - 5);tr.right = _x+50;tr.bottom = _y+50;}else {tr.left = cos(HD (_t - 5 < 0 ? 355 : _t - 5)) (R);tr.top = sin(HD (_t - 5 < 0 ? 355 : _t - 5)) (R);tr.right = cos(HD (_t + 5)) (R - 70);tr.bottom = sin(HD (_t + 5)) (R - 70);}//大略地绘制12个数字,如果有更好的定位办法欢迎奉告,感激。
drawtext(s, &tr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

//绘制60个小刻度setlinestyle(PS_SOLID | PS_ENDCAP_SQUARE, 3);double xx = 0;for (int j = 0, i = 0; i < 60; i++) {j = i - 15;if (j < 0) { j = 60 + j; }if (j % 5 == 0) { continue; }xx = HD j 6;line(cos(xx) (R - 10), sin(xx) (R - 10), cos(xx) (R - 5), sin(xx) (R - 5));}//绘制圆心fillcircle(0, 0, 8);

效果图:

绘制大小刻度和圆心

绘制指针

指针有三种,时针粗短,分针适中,秒针苗条,示例中将秒针绘制成赤色以示差异。
指针的位置是根据韶光动态更新的,以是先要获取当前的系统韶光,可以大略地引入time.h头文件。

//绘制时针,参数为当前时,分,秒void drawHp(int h,int m,int s) {h -= 3;if (h < 0) {h = 12 + h;}double hd = HD h 30 + PI/360 m +PI/21600 s;int _x = cos(hd) (R_2+10);int _y = sin(hd) (R_2+10);drawPt(_x, _y, 8);}//绘制分针,参数为当前分,秒void drawMp(int h,int s) {h -= 15;if (h < 0) {h = 60+h;}double hd = HD (double)(h + s / static_cast<double>(60)) 6;int _x = cos(hd) (R_2+40);int _y = sin(hd) (R_2+40);drawPt(_x, _y, 5);}//绘制秒针,参数为当前秒void drawSp(int h) {h -= 15;if (h < 0) {h = 60 + h;}double hd = HD h 6;int _x = cos(hd) (R_2 + 120);int _y = sin(hd) (R_2 + 120);drawPt(_x, _y, 2);}//根据指针x、y坐标真实绘制指针void drawPt(int x, int y,int w) {if (w == 2) {setlinecolor(RED);}setlinestyle(PS_SOLID | PS_JOIN_ROUND | PS_ENDCAP_ROUND, w);line(0, 0, x, y);setlinecolor(BLACK);}

调用办法:

time_t now = time(NULL);struct tm info;localtime_s(&info,&now);//得到本地韶光int hour = info.tm_hour;int minute = info.tm_min;int second = info.tm_sec;drawHp(hour,minute,second);drawMp(minute,second);drawSp(second);

让我们看看效果如何。

绘制了指针的时钟,涌现了好多的秒针轨迹

哈哈,闹笑话了。
由于我们在实时地?打算新的指针坐标并更新绘制新的指针位置,因此界面上绘制出了很多的秒针轨迹。
怎么办理呢?方法肯定是有的,可以绘制前先打消设备(调用cleardevice()函数,将打消全体绘图窗口),我们这里不打算打消全部,只把钟面圆框内的部分打消:

clearcircle(0, 0, oY-25);//先打消内圆框之内的全部//然后再写动态绘图刻度和指针的代码

看看效果如何:

到此为止,一个大略的时钟已经绘制完成了。
但是你可能创造有点小问题:界面有时会闪烁一下。
这是由于动态更新指针位置并实时绘制出来造成的闪烁,可以使得经典的双缓冲技能来办理。
EasyX很知心肠供应了这个技能,只须要三个函数:

BeginBatchDraw();while(true){ //反复的绘制操作FlushBatchDraw();}EndBatchDraw();

便是这么大略。
在我们的示例代码中加上它们就行了。

大略优化:节流

示例中将动态韶光打算和实时更新绘图都放在了while循环中,打算机超强的打算速率下,每秒可将司帐算非常多次,示例中时钟是按秒走的,我们可以采取节流思想,在一段韶光的指定时间间隔内只实行一次回调函数,限定在一定韶光内的实行次数,从而掌握高频率触发的事宜,避免过多的打算或操作影响性能。
最大略的做法是一次绘图更新完成后,sleep一定韶光。
也可以根据系统时钟打点数打算fps:

const clock_t FPS = 1000 / 2;//每秒只实行两次clock_t startTime, freamTime;while (true) {//打算帧率startTime = clock();freamTime = clock() - startTime;if (freamTime < FPS){Sleep(FPS - freamTime);} //将各种动态绘图代码写在此处}完全代码(Visual stdio 2022 编译通过)

#include<graphics.h>#include<math.h>#include<time.h>#define W 800//窗口宽度#define H 620//窗口高度#define oY H/2//调度坐标原点y为窗口高度的一半#define R 290//时钟内圆半径#define PI 3.14#define HD PI/180#define R_2 R/2//绘制时钟钟面void drawFace();//绘制时针void drawHp(int h,int m,int s);//绘制分针void drawMp(int h,int s);//绘制秒针void drawSp(int h);void drawPt(int x, int y,int w);int main() {initgraph(W, H,0);SetWindowText(GetHWnd(),_T("动态时钟"));//调度坐标原点为窗口中央,方便绘图setorigin(W / 2, oY);//设置坐标原点,以窗口中央点当作坐标原点(0,0)setbkcolor(WHITE);cleardevice();setlinecolor(BLACK);//设置线条颜色setlinestyle(PS_SOLID, 10);//设置线条样式和宽度circle(0, 0, oY - 10); //绘制时钟外黑框//画出时钟的内框setlinestyle(PS_SOLID, 4);circle(0, 0, R);setlinestyle(PS_SOLID, 1);const clock_t FPS = 1000 / 2;//每秒实行两次clock_t startTime, freamTime;BeginBatchDraw();//开启双缓冲绘图功能,防止界面闪烁while (true) {//打算帧率startTime = clock();freamTime = clock() - startTime;if (freamTime < FPS){Sleep(FPS - freamTime);}clearcircle(0, 0, oY-25);//将须要动态绘制的部分打消掉time_t now = time(NULL);struct tm info;localtime_s(&info,&now);int hour = info.tm_hour;int minute = info.tm_min;int second = info.tm_sec;drawFace();drawHp(hour,minute,second);drawMp(minute,second);drawSp(second);FlushBatchDraw();}EndBatchDraw();closegraph();return 0;}void drawHp(int h,int m,int s) {h -= 3;if (h < 0) {h = 12 + h;}double hd = HD h 30 + PI/360 m +PI/21600 s;int _x = cos(hd) (R_2+10);int _y = sin(hd) (R_2+10);drawPt(_x, _y, 8);}void drawMp(int h,int s) {h -= 15;if (h < 0) {h = 60+h;}double hd = HD (double)(h + s / static_cast<double>(60)) 6;int _x = cos(hd) (R_2+40);int _y = sin(hd) (R_2+40);drawPt(_x, _y, 5);}void drawSp(int h) {h -= 15;if (h < 0) {h = 60 + h;}double hd = HD h 6;int _x = cos(hd) (R_2 + 120);int _y = sin(hd) (R_2 + 120);drawPt(_x, _y, 2);}void drawPt(int x, int y,int w) {if (w == 2) {setlinecolor(RED);}setlinestyle(PS_SOLID | PS_JOIN_ROUND | PS_ENDCAP_ROUND, w);line(0, 0, x, y);setlinecolor(BLACK);}void drawFace() {//绘制12个大刻度,利用白色实心小圆setfillcolor(BLACK);double _x, _y;TCHAR s[3];settextcolor(BLACK);LOGFONT f;gettextstyle(&f);f.lfHeight = 36; //设置字体大小_tcscpy_s(f.lfFaceName, _T("serif")); //设置字体名称f.lfQuality = ANTIALIASED_QUALITY;//设置字体平滑效果settextstyle(&f);for (int j = 9, i = 0; i < 12; j++, i++) {if (i > 2) { j = i - 3; }int _t = j 30;_x = cos(HD _t) (R - 5);_y = sin(HD _t) (R - 5);fillcircle(_x, _y, 5);//绘制大刻度swprintf_s(s, _T("%d"), i == 0 ? 12 : i);RECT tr;//定义笔墨位置if (j == 10) {//1时tr.left = _x - 50;tr.top = _y;tr.right = _x;tr.bottom = _y + 50;}else if(j==4){//7时tr.left = _x +50;tr.top = _y;tr.right = _x;tr.bottom = _y -50;}else if (j == 1) {//4时tr.left = _x - 50;tr.top = _y - 50;tr.right = _x;tr.bottom = sin(HD (5+_t)) (R - 5);}else if( j == 7) {//10时tr.left = _x+10;tr.top = sin(HD (5 + _t)) (R - 5);tr.right = _x+50;tr.bottom = _y+50;}else {tr.left = cos(HD (_t - 5 < 0 ? 355 : _t - 5)) (R);tr.top = sin(HD (_t - 5 < 0 ? 355 : _t - 5)) (R);tr.right = cos(HD (_t + 5)) (R - 70);tr.bottom = sin(HD (_t + 5)) (R - 70);}drawtext(s, &tr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);}//绘制60个小刻度setlinestyle(PS_SOLID | PS_ENDCAP_SQUARE, 3);double xx = 0;for (int j = 0, i = 0; i < 60; i++) {j = i - 15;if (j < 0) { j = 60 + j; }if (j % 5 == 0) { continue; }xx = HD j 6;line(cos(xx) (R - 10), sin(xx) (R - 10), cos(xx) (R - 5), sin(xx) (R - 5));}//绘制圆心fillcircle(0, 0, 8);}