ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言图形库入门:EGE基础绘图从配置到实践

C语言图形库入门:EGE基础绘图从配置到实践 EGE这个库很多初学者第一次听到容易懵——它是Easy Graphics Engine的缩写一个面向C/C的轻量级图形库专门用来在Windows下快速画点、画线、画圆、搞交互。不像OpenGL、DirectX那样一上来就是渲染管线、着色器EGE把“画出窗口、画出图形”这件事简化到了极致。你只需要会C语言的基本语法再配合几个绘图函数就能做出像模像样的图形程序。我最早接触EGE是在帮人做课设的时候那时候懒得在MFC里折腾消息循环用EGE几分钟就能出一个能看的效果。后来陆陆续续在不少项目里用过它做原型、做教学演示甚至做了一些小的算法可视化工具。说实话这个库的定位就是“给想专注逻辑和创意的人省掉窗口编程的麻烦”。如果你是编程初学者想直观感受“代码能画出东西”的乐趣或者你是老师/学生需要快速做图形化演示又或者你只是想在C语言项目里加一个不复杂的图形界面——EGE都是一个很顺手的起点。这篇主要讲绘图的基础知识环境怎么搭、窗口怎么开、坐标和颜色怎么理解、基本图形怎么画还会把几个我踩过的坑一并说清楚。1. 项目概述与选型思路1.1 EGE到底解决了什么问题很多人有疑问C语言本身不带图形函数想画图怎么办早期教材常用Turbo C的graphics.h那是DOS时代的产物在现代Windows上根本跑不起来。EGE就是在Windows环境下用C/C提供一套类似graphics.h风格的图形接口但它跑在Windows窗口里支持鼠标键盘事件支持真彩色还支持透明、双缓冲等较新的特性。它的核心价值在于“低门槛”。你不需要先去学Win32 API、不需要了解窗口类的注册和消息循环只要包含一个头文件调用initgraph就能创建一个窗口。这个抽象层的存在让绘图教学、算法可视化、简单游戏开发都变得非常直接。我个人的看法是EGE特别适合以下三类场景教学场景数据结构、图形学入门课里用EGE展示算法的过程和结果比纯命令行直观得多。课程设计/毕设原型需要快速做一个带简单界面的演示程序EGE比Qt、MFC快得多代码量也小得多。个人兴趣项目比如制作小工具、模拟器、简易图形应用不想被繁杂的框架束缚。1.2 和同类绘图库相比EGE的优势与劣势在C/C领域能画图的库其实不少但各有侧重。下表是我根据实际使用体验整理的对比库/方案上手难度适用场景说明EGE很低C/C初学者、教学、快速原型接口风格接近老式graphics.h封装了Windows窗口细节Win32 GDI较高Windows原生软件开发功能强大但需要写大量样板代码Qt较高大型跨平台应用能力强但体积和学习成本都大SDL2中等游戏开发、多媒体应用跨平台事件模型清晰但要自己处理窗口上下文OpenGL很高高性能图形、游戏渲染强调渲染管线和GPU不适合入门画图选EGE最大的理由不是它最强而是它在“能快速画出东西”和“还有一定扩展性”之间取得了平衡。它的底层也是调用GDI但帮你把繁琐的初始化、窗口管理、设备上下文获取全部封装掉了你只管画。对于初学者来说这种“开箱即用”的体验非常友好。2. 环境准备与开发配置2.1 开发环境选型Dev-C、Visual Studio还是Code::BlocksEGE官方提供了针对多个常见IDE的安装包我自己实测下来用得最顺的是Visual Studio系列和Dev-C。如果你的机器上已经装了Visual Studio2017及以上版本直接用NuGet搜ege就能安装集成这是最省事的路径。如果只是为了学基础绘图、不打算长期做大型项目Dev-C或Code::Blocks搭配EGE的完整安装包也可以配置起来其实就三步下载、安装、在工程里链接库。我个人建议新手优先选Visual Studio。原因有二一是EGE在VS上的适配最完善调试体验好报错信息直观二是VS自带NuGet安装EGE只需要鼠标点几下不用手动复制头文件和库文件避免了不少路径配置上的坑。2.2 安装EGE库的详细步骤这里以Visual Studio为例说下我常用的安装流程创建一个新的“控制台应用”项目C。在“工具”菜单里打开“NuGet包管理器”选择“管理解决方案的NuGet程序包”。在“浏览”标签页搜索ege找到ege作者是Misaka_0x897或相关维护者点击“安装”。装完会自动把graphics.h、ege.h等头文件和库文件引用进来。在代码里#include graphics.h就可以开始绘图的编码了。如果你用的是Dev-C可以到EGE官网下载对应的安装包它会自动配置到编译器的include和lib目录。安装完后写第一段测试代码能出一个窗口就算成功。2.3 第一段测试代码验证环境是否正常测试环境的代码越简单越好哪怕只创建一个窗口再退出。C版本里EGE的命名空间是ege但为了兼容graphics.h的写法常用using namespace ege;或者直接用宏INITGRAPH之类的封装。下面是最简单的例子#include graphics.h int main() { initgraph(640, 480); // 创建一个640x480的窗口 circle(320, 240, 100); // 画一个圆心在(320,240)、半径100的圆 getch(); // 等待按键 closegraph(); // 关闭窗口 return 0; }编译运行后屏幕上会弹出一个窗口里面画着一个圆圈。看到这个效果就说明EGE安装成功、链接无误。这里有两个细节值得注意一是EGE的initgraph在创建窗口时会默认设置背景为黑色可以用setbkcolor和cleardevice改变并刷新二是getch()用来暂停程序否则窗口会一闪而过。3. 绘图基础窗口、坐标与颜色3.1 窗口的创建与销毁以及背后的逻辑initgraph是EGE里最先执行的函数它的原型大致是void initgraph(int width, int height, int flag INIT_DEFAULT);前两个参数是窗口的宽和高单位是像素。第三个参数flag用来指定窗口的初始化标志比如INIT_NOBORDER可以创建无边框窗口INIT_RENDERMANUAL表示手动控制刷新INIT_ANIMATION用于动画模式等。我常用的做法是在程序最开头调用initgraph在程序末尾调用closegraph。closegraph会释放EGE申请的图形资源包括窗口句柄和设备上下文。如果你不调用closegraph在某些异常退出时可能会留下窗口残留或资源泄漏所以养成“initgraph和closegraph成对出现”的习惯很重要。使用initgraph时还有个常见的“糊涂点”width和height是指窗口客户区的宽高不包括标题栏和边框。如果你的屏幕分辨率是1920x1080创建一个1920x1080的窗口标题栏会超出屏幕显示范围所以布局时最好留出一些余量。3.2 坐标体系从左上角开始的二维世界EGE的坐标和大多数Windows图形系统一样原点在窗口的左上角X轴向右为正Y轴向下为正。这和数学课上的笛卡尔坐标系不一样——Y轴是反向的。很多新手第一次画图明明算对了坐标画出来却是倒的就是因为忽略了这一点。比如想画一个从点(0,0)到点(100,0)的直线它会沿窗口上边缘水平向右延伸。想画一条往“上”走的线y坐标反而是减小的。我把这个坐标方向称为“屏幕坐标”几乎所有Windows绘图库都是这个约定EGE也不例外。理解了这一点后面不管画什么图形坐标计算就不会跑偏。在实操层面我通常会把“逻辑坐标”和“屏幕坐标”分开看。如果你在写算法可视化算法里用的坐标往往是数学坐标Y轴向上那么在调用EGE绘图函数之前先做一个变换int screenY height - 1 - logicalY;这个小转换就能避免后续大量坐标系混用的痛苦。越是看似简单的东西越容易在大型代码里埋坑坐标变换一定要尽早统一。3.3 颜色的表示EGE的颜色编码规则EGE的颜色是32位整型值用EGERGB或EGEARGB宏来构造。最常见的宏是COLORREF EGERGB(BYTE red, BYTE green, BYTE blue);参数范围是0~255。例如EGERGB(255, 0, 0)表示纯红色EGERGB(0, 255, 0)是绿色EGERGB(0, 0, 255)是蓝色。如果使用EGEARGB则多加一个透明度参数alpha用于透明混合效果。绘制之前用setcolor设置前景色用setfillcolor设置填充色。有一点要特别说明EGE里setcolor影响的是线条、边框、文字颜色setfillcolor影响的是填充区域的颜色。两者作用不同混用是新手常犯的错。比如你用setfillcolor(EGERGB(255,0,0))去画一个rectangle如果没显式设置填充方式可能只看到边框内部还是黑色的因为默认填充风格是“填充满”还是“不填充”取决于当前填充模式。填充模式由setfillstyle控制它接受的类型包括SOLID_FILL实心填充HOLLOW_FILL空心填充BKSLASH_FILL等各种花纹填充基础绘图里我们最常用的是SOLID_FILL。下一个小节会看到它和填充图形搭配的效果。4. 基本图形的绘制与实操要点4.1 点、直线、折线一切图形的基础点在EGE里是putpixel函数void putpixel(int x, int y, COLORREF color);它的作用就是把指定位置的像素点设置为指定颜色。虽然画点很简单但在算法可视化里它常被用来逐像素绘制函数曲线、直方图、粒子轨迹等。用多个点密集排列就能模拟出一条线的效果。画直线用的是linevoid line(int x1, int y1, int x2, int y2);注意它的参数顺序是端点1、端点2。连续绘制多条线段组成折线时可以用polyline或polygon。polyline只画边不封口polygon会首尾相连形成封闭图形。这两个函数都接收一个int*类型的点数组和点的数量下面是例子int pts[] {100, 100, 200, 100, 150, 200}; polyline(pts, 3); // 三个点两段相连的线段实际操作中我建议把坐标点放在数组里统一管理这样无论修改还是复用都很方便。如果要画多个独立的图形就直接分开调用对应的绘图函数代码可读性更高。4.2 矩形、圆与椭圆常用几何图形的画法EGE里画矩形用rectanglevoid rectangle(int left, int top, int right, int bottom);注意这里四个参数代表“左上角”和“右下角”的坐标而不是“起点宽高”。如果习惯用宽高自己封装一层void rectFromLTRB(int x, int y, int w, int h) { rectangle(x, y, x w - 1, y h - 1); }为什么会多一个-1因为right和bottom是包含边界像素的在精确绘制时如果矩形宽为wx方向是从x到xw-1这样更符合像素网格的逻辑。画圆用circlevoid circle(int x, int y, int radius);参数分别是圆心横坐标、圆心纵坐标、半径。EGE还提供了ellipse参数是外接矩形的左上角和右下角void ellipse(int left, int top, int right, int bottom);例如ellipse(100, 100, 300, 200)画出的椭圆外接矩形宽度是200像素高度是100像素圆心在(200, 150)。这个函数很适合画横椭圆或竖椭圆。画完轮廓之后如果需要填充可以用fillcircle、fillrectangle、fillellipse等函数。它们的参数和对应轮廓函数完全一致区别在于绘制时会按当前填充设置填充内部区域。所以需要“先画边框、再填色”时可以这样组合setcolor(EGERGB(0, 0, 255)); // 蓝色边框 setfillcolor(EGERGB(255, 0, 0)); // 红色填充 fillcircle(200, 200, 80);这里fillcircle会同时画边框和填充色如果不想显示边框单独设置边框颜色和填充颜色为同一色即可。4.3 上色与填充让图形真正“立体”起来只画线框的画程序看起来总是差了点意思。填充色是图形应用里表现力的关键。EGE的填充流程一般是三步setfillcolor设置填充颜色。setfillstyle设置填充样式一般用SOLID_FILL。调用fillxxx函数绘制填充图形。我在做“贪吃蛇”小游戏时喜欢用setfillstyle(SOLID_FILL)配fillrectangle来画蛇身方块和食物因为实心矩形看起来更干净也更容易检查碰撞边界。一个常见误操作是调用了fillcircle后觉得内部还是空的排查半天发现是setfillstyle没调用或者被设成了HOLLOW_FILL。还有一个颜色相关的细节每次调用setcolor或setfillcolor后后续所有相关绘图操作都会受其影响直到再次调用设置函数。所以如果你交替绘制不同颜色的图形要记得在每个图形绘制前设置相应的颜色避免颜色串了。这个“状态持续”特性来自GDIEGE沿用下来初学时容易忽略。4.4 一个完整的示例程序绘制简单几何图形把上面提到的内容串起来写一个小程序在640x480窗口里画一个矩形、一个圆、一个椭圆并分别填上不同颜色。#include graphics.h #include conio.h int main() { initgraph(640, 480); // 设置背景为淡灰色 setbkcolor(EGERGB(230, 230, 230)); cleardevice(); // 画一个蓝色边框的红色实心矩形 setcolor(EGERGB(0, 0, 255)); setfillcolor(EGERGB(255, 0, 0)); setfillstyle(SOLID_FILL); fillrectangle(50, 50, 200, 150); // 画一个绿色边框的黄色实心圆 setcolor(EGERGB(0, 255, 0)); setfillcolor(EGERGB(255, 255, 0)); fillcircle(350, 100, 60); // 画一个紫色边框的青色实心椭圆 setcolor(EGERGB(128, 0, 128)); setfillcolor(EGERGB(0, 255, 255)); fillellipse(450, 250, 100, 60); // 画几条线 setcolor(EGERGB(0, 0, 0)); line(50, 200, 400, 300); getch(); closegraph(); return 0; }这段代码运行后你能直观看到颜色、填充和线框之间的区别。试着改改参数——比如把fillrectangle改成rectangle看看边框和填充的变化有助于快速建立记忆。5. 文字绘制与字体处理5.1 用outtextxy在窗口里显示文字绘图界面里只有图形不够很多时候还要显示文字分数、状态提示、坐标信息等。EGE提供outtextxyvoid outtextxy(int x, int y, LPCTSTR str);它会使用当前字体设置在指定的坐标位置显示字符串。这里有个重要细节outtextxy的坐标指的是字符串中第一个字符的“左上角”位置而不是字符中心或基线。如果你想将文字居中需要先用textwidth和textheight测量字符串的尺寸再做偏移计算。一个实用封装void drawCenterText(int x, int y, const char* str) { int w textwidth(str); int h textheight(str); outtextxy(x - w / 2, y - h / 2, str); }这样就可以把字符串的中心对准(x, y)输出。对于界面布局来说这种居中操作非常常用。5.2 中文字体显示的坑与解决方案EGE默认的字体可能不支持中文直接输出中文字符串时容易变成乱码或者方块。解决办法是设置中文字体比如setfont(20, 0, 宋体);setfont的参数第一个是字符高度第二个是字符宽度0表示按比例自动第三个是字体名称。这里的“宋体”可以换成“微软雅黑”“黑体”等系统字体。设置之后再用outtextxy输出中文就不会乱码了。还有一个细节是在VS工程里要保证源文件的编码格式和编译器的字符集设置匹配。如果遇到“中文乱码”先看是不是字体没设置再看字符集是不是Multi-Byte或Unicode的问题。EGE在较新的版本里对宽字符支持得不错使用L中文配合setwtext或直接使用setfont宽字符输出也是可行路径。我在实际项目里通常统一使用LPCWSTR来避免编码转换的混乱不过基础入门阶段设置好字体之后用char*基本也够了。6. 常见问题速查与排查实录6.1 窗口一闪而过看不到画面这个问题十有八九是因为主函数结束时窗口被立即关闭。解决方式是在关闭图形窗口前加上等待getch(); // 或者 Sleep(3000);getch()需要包含conio.h它的行为是等待按键更适合“按任意键退出”的场景。用Sleep则会让程序静止几秒自动关闭适合不需要交互的场景。我个人习惯用getch()因为可以看到结束后的窗口状态按一下键才退出逻辑也更接近游戏里的“开始”/“结束”处理。6.2 中文显示成乱码或矩形块原因通常有三个没有设置中文字体使用默认英文字体去显示中文。源文件编码和编译器字符集不一致。使用了窄字符接口去传宽字符内容。排查时先执行setfont(20, 0, 微软雅黑)再看输出。如果还乱码检查编译选项中字符集设置建议使用“使用Unicode字符集”。基础绘图阶段我推荐统一用char*字符串配合多字节字符集往往能快速解决问题。还有一种可能是IDE的“保存为带有BOM的UTF-8”格式导致编译器识别异常调整源文件编码也能修复。6.3 画出来的图形位置不对或方向反了绝大多数情况是坐标系没弄清楚。EGE的Y轴向下为正绘制“向上”的图形时要计算好坐标。比如你想让一个物体从下方往上移动y坐标应该递减而不是递增。另外rectangle的参数是“左上角”和“右下角”如果你传入的是(100, 200, 80, 80)就得不到宽度为负的矩形而是可能得到一个不显示或充满异常区域的图形。图形API通常要求left righttop bottom调用前最好做一次参数校验。如果你发现图形方向反了尤其是做算法可视化时建议在数据交给绘图函数前统一做一次坐标映射把所有逻辑坐标转换为屏幕坐标不要在每个函数里零散地处理否则最后很难维护。6.4 绘图闪烁或刷新不及时EGE自带的默认刷新策略在绘制复杂图形时可能产生闪烁。解决方案是使用双缓冲先把所有内容绘制到内存缓冲区再一次性地把缓冲区渲染到窗口。EGE里可以这样做初始化时使用INIT_RENDERMANUAL标志手动控制帧渲染。用beginrender()和endrender()包裹绘图逻辑实现双缓冲。示例initgraph(640, 480, INIT_RENDERMANUAL); while (true) { beginrender(); // 绘制所有图形 endrender(); // 处理输入或等待 }这种方式对实现动画、游戏循环特别重要。我第一次写动画时没开手动渲染画面一直闪后来用beginrender/endrender包裹后立刻流畅了。如果你只是静态绘图默认模式就够用不需要额外开启。6.5 快速排查表现象最常见原因解决思路窗口一闪而过缺少暂停等待添加getch()中文乱码未设置中文字体使用setfont(20, 0, 微软雅黑)图形位置不对Y轴方向理解错误检查坐标变换逻辑填充不显示填充样式被设为空心调用setfillstyle(SOLID_FILL)颜色不对设置了颜色但未重新设置每次绘制前确认当前颜色窗口无法关闭忘记closegraph在退出前释放资源动画闪烁默认刷新方式不适合使用手动渲染双缓冲7. 绘图之外的扩展能力与选型心得7.1 鼠标键盘交互EGE不是只能拿来“画静态图”它同样支持鼠标和键盘事件。核心函数包括mousemsg()获取当前鼠标消息。kbhit()和getch()键盘输入检测。flushmouse、flushkey清除未处理的鼠标/键盘消息。我做过一个用方向键控制移动的小例子思路就是循环里不断检查kbhit再根据按键更新物体坐标最后重新绘图。这部分在“入门篇二”里应该会展开讲但既然提到EGE能做什么交互能力一定要提一嘴——因为很多初学者画了半天静态图却不知道怎么让画面“动起来”其实EGE对事件的处理比想象中简单接口风格很统一。7.2 从EGE迁移到更复杂的图形库EGE的价值不止于它本身更在于它帮你建立了图形编程的基础心智模型窗口、坐标、颜色、事件、双缓冲。这些概念在SDL2、SFML、Qt乃至游戏引擎里都能找到对应物。等你在EGE里理解了“绘图循环”“事件分发”“资源释放”这些核心概念以后换到其他库或引擎不会觉得特别困难。就我自己的经历来说EGE就像软件渲染和图形学之间的一座小桥。它不会让你直接接触到GPU、顶点缓冲这些底层但它成功地把“用代码画东西”变成了一件可以快速验证、快速迭代的事。对于想走图形方向的人这是一个温和的起点对于只想用图形辅助其他项目的人它也是够用的工具。7.3 我个人的一点使用体会如果让我给EGE下一个判断我会说它的上限不高但下限足够低。真要做高强度大型游戏或复杂图形应用EGE并不是首选但如果你想在几行代码内看到结果、想验证一个算法画出来是什么样、想带学生快速跨过“会语法但不会做项目”的坎EGE在轻量级方案里确实相当顺手。我在做数据结构课的可视化演示时遇到过很多次“本来打算用Qt写个界面结果两天过去了还在调布局”的情况。后来换用EGE半小时就画出了链表、二叉树的节点图效果还不错。不是说EGE比Qt更强大而是它在特定场景下更对症。这些年我越来越觉得工具选型从来不是“越强大越好”而是“越顺手越好”。EGE就是那个能让初学者“顺手”走完第一公里的工具。最后再分享一个小技巧写EGE代码时别急着把功能全堆main函数里。把“画某一类图形”“初始化某一画面”的逻辑封装成独立函数传参用坐标和颜色这样后期扩展和调试都会轻松很多。基础篇的这些东西练熟之后后面再做动画、做交互你会发现自己已经站在一个很踏实的起点上了。
返回列表