简介:面向C++初学者的图形编程组件包,用于解决Windows环境下搭建图形绘制环境、调用绘图接口的问题。压缩包共含2个文件:1个.lib静态链接库提供图形功能预编译实现,1个.h头文件声明函数、常量、类接口,整体仅11KB,轻量且便于配置。将库加入工程并包含头文件后,即可使用初始化图形模式、绘制直线/矩形/圆形等基本图形、设置前后景色、输出文本、等待按键等常用API,覆盖从创建窗口到关闭图形会话的完整调用流程。包内头文件清晰列出可用接口,lib库可直接参与链接,适合课程设计、小型可视化练习以及初学者理解图形库配置逻辑。使用过程中,读者可通过头文件查阅坐标系统、颜色枚举等设计约定,遇到链接错误时也能按lib与头文件对应关系快速排查。目前已有3587人学习/下载,作为快速体验C++图形编程的入门素材,具有较高的直接可用性。
1. C++ graphics 图形绘制圈里传了很久的包,到底能不能直接用
做 C++ graphics 图形绘制的人,多少都听过 graphics.h 和 graphics.lib 这对老搭档。标准库连一根直线都不给你画,而这份下载包里把图形库的头文件和静态库一次性配齐了,装进 Dev-C++ 或 Code::Blocks 就能跑起 initgraph、line、circle 这一整套 BGI 绘图函数。它最大的吸引力在于轻:不用引入 Qt、OpenGL 这种重型框架,一个图形库文件加十几个 API 就能把课程设计的界面、演示动画撑起来。不过有一点得提前说明白,这个包源自 Borland 时代的 BGI 图形库,坐标原点在屏幕左上角、Y 轴向下,一切绘制都发生在控制台窗口的绘图区里,跟现代 GUI 窗口完全是两套思路。别拿 OpenGL 的预期来比它,把它当成一块能画点、画线、填色、做简单动画的黑板就好。
2. 把 graphics.lib 接进编译器:include 路径、链接名与两套环境配置
2.1 先拆包认清版本:Borland 原版还是 WinBGIm 适配版
下载包拿到手先别急着复制文件,花两分钟确认它是哪个版本的图形库。这里有个容易翻车的差别:Borland 原版 BGI 的 graphics.lib 是给 Turbo C / Borland C++ 用的 16 位静态库,头文件里到处是 far、near、unsigned int 这类老式声明。而目前网上流传最广、能直接在 Dev-C++、Code::Blocks(MinGW 编译器)里跑通的,其实是 WinBGIm 适配版,它把 graphics.h 的接口保留下来,底层改调 Windows GDI,链接名通常对应 libbgi.a 或者你的包里的 graphics.lib。
判断方法很直接:打开头文件看最前面的注释和预编译指令。WinBGIm 版本一般有#include <windows.h>或者大段#ifdef __cplusplus包起来的导出声明,Borland 原版看到的是清一色的 C 函数原型和一个#define DETECT 0。再瞄一眼文件大小,WinBGIm 版头文件动辄 30KB 以上,原版大多 10KB 上下。这一步判断为什么重要?因为两个版本的链接参数完全不一样,原版在 MinGW 下直接链基本会报一堆未定义引用,白折腾一小时。
2.2 Dev-C++ 环境:目录拷贝与链接参数怎么写
Dev-C++ 是最常见的复现环境,配置思路是把库文件塞进编译器自带的库目录里,让每个项目都能找到它。操作分四步:
第一步,找到 Dev-C++ 的安装目录,一般是C:\Dev-Cpp\MinGW32\include和C:\Dev-Cpp\MinGW32\lib。不同版本路径略有差异,直接在 Dev-C++ 菜单栏点「工具 > 编译选项 > 目录」,能看到编译器实际使用的头文件目录和库目录,照那个路径放就行。
第二步,把 graphics.h 复制到 include 目录,注意别放进include\c++\...那种多级目录,就放最顶层 include 根目录即可。
第三步,把 graphics.lib 复制到 lib 目录,同时做一次重命名。MinGW 的 gcc 在链接静态库时,默认找libxxx.a格式的文件名,你直接写-lgraphics,它找的是libgraphics.a,跟你的 graphics.lib 对不上。所以复制一份并重命名成libgraphics.a,原来的 graphics.lib 也留着,反正两个名字在同一目录下不冲突。
第四步,新建项目后,在「项目 > 项目属性 > 参数 > 链接器」里追加链接参数:
-lgraphics -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32这套参数是 WinBGIm 在 MinGW 下的标准依赖组合。-lgraphics让你重命名好的静态库被链接进来,后面那一串是 Windows 的 GDI 相关系统库。如果你的包是原版 BGI,不需要这些系统库,但大概率会遇到运行时报错,这种情况建议直接去下一个 WinBGIm 适配版替代。
配置完写一段最小验证代码:
#include <graphics.h> int main() { int gd = DETECT, gm; initgraph(&gd, &gm, ""); setcolor(WHITE); line(100, 100, 300, 100); getch(); closegraph(); return 0; }这段代码的逻辑很简单:DETECT让图形库自动探测当前显示模式,initgraph完成绘图区初始化,line在坐标 (100,100) 到 (300,100) 之间画一条水平线,getch()挂住控制台等用户按任意键,最后closegraph释放图形模式。如果编译、运行都通过并且屏幕中间出现一条白线,说明环境已经打通。
这里有个细节值得留意:initgraph第三个参数传空字符串"",表示图形驱动和字体文件在当前工作目录找。在 Dev-C++ 里工作目录通常就是源文件所在目录,所以没事;一旦你换了 IDE 或者开启了不同启动目录,就可能因为找不到驱动而失败。后续会专门讲这个问题。
2.3 Code::Blocks 环境:路径差异与全局链接器设置
Code::Blocks 的配置路径和 Dev-C++ 有个本质区别:它倾向于通过工程设置去指向库文件,而不是把文件拷贝到编译器目录里,这样可以让多个不同版本的 graphics 库共存,切换工程时互不污染。
操作路径在「Settings > Compiler > Search directories」里,分两个 Tab:Compiler 和 Linker。Compiler 里添加 graphics.h 所在目录,Linker 里添加 libgraphics.a 所在目录。这里不用靠重命名去迁就编译器,也不需要拷贝文件,直接通过「Linker settings」里的 Link libraries 添加那个.a文件即可。如果还报系统库缺失,同样在 Linker 的 Other linker options 里加上-lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32。
选 Dev-C++ 还是 Code::Blocks?我的建议是:如果你只做一个小项目,Dev-C++ 的目录拷贝法最快;如果手里有多个课程设计要用图形库,Code::Blocks 的工程级配置更干净,卸载、换版本都在工程设置里点几下,不用回头翻系统目录清理垃圾文件。
三种环境的情况汇总一下:
| 环境 | 头文件放置方式 | 库文件放置方式 | 关键链接参数 |
|---|---|---|---|
| Dev-C++ | 复制到 include 根目录 | 复制并重命名为 libgraphics.a | -lgraphics -lgdi32 ... |
| Code::Blocks | 工程内指定搜索目录 | 工程内指定链接文件 | 面板添加 .a 文件 |
| VC6 / VS | 工具 -> 选项 -> 目录 | 工具 -> 选项 -> 目录 | 工程设置里输入 graphics.lib |
VC6 老环境不在本次包的直接适配范围里,但思路一致:把头文件和 lib 文件路径分别填进 include 和 lib 搜索目录,再在工程链接器输入框加一个graphics.lib。需要注意 VS2010 之后的版本对老式 C 库兼容性很差,实测不如 MinGW 环境顺畅。
2.4 静态链接背后的原理:为什么改个名字能省下大量麻烦
新手常问一个问题:为什么 dev-C++ 里明明能直接用 windows.h,却偏偏要对 graphics.lib 又是改名又是指定路径?这里补一段原理。graphics.lib 是静态库文件,它的本质是一堆已经编译好的.o目标文件的归档包。MinGW 的链接器在扫描-lgraphics时,依赖一套命名约定去寻找文件,也就是lib<名字>.a或lib<名字>.lib。你手上的 graphics.lib 不符合这个模式,链接器自然假装看不见。
另外很多人忽视了一个点:WinBGIm 适配版的 graphics.lib 虽然调用的是 Windows GDI,但为了保持 BGI 的函数名语义,库内部做了完整封装。所以链接时不光要-lgraphics,还需要把库依赖的 GDI 核心系统库一并拉进来。这就是-lgdi32系列参数的由来。如果没有这些系统库,链接器会报一堆undefined reference,细看全是 CreateCompatibleDC、TextOutA 这种 Windows API,一眼就能认出来。
3. 图形绘制核心实践:坐标系、初始化流程与常用绘图函数
3.1 initgraph 的参数怎么填:DETECT、驱动号与画布分辨率
graphics.h 编程第一道门槛就是 initgraph。它的完整原型是initgraph(int *gdriver, int *gmode, char *path),三个参数一个都不能含糊。
gdriver指图形驱动类型。传入DETECT(值为 0)时,库会自动探测硬件显示能力并设置对应最高分辨率。你也可以显式指定驱动号,比如填写VGA、EGA,但这种写法常见于老式教程,实际在 WinBGIm 适配版里,驱动类型已经被重新映射,DETECT几乎是最稳的选择。
gmode是显示模式。即便用DETECT,这个参数也得传一个整型变量的地址,函数会把探测到的模式值回填进去。手动指定模式时,模式决定分辨率,比如:
| 驱动 | 模式 | 分辨率 | 颜色数 |
|---|---|---|---|
| VGA | VGAHI | 640x480 | 16 |
| VGA | VGAMED | 640x200 | 16 |
| EGA | EGAHI | 640x350 | 16 |
WinBGIm 下的模式和传统 BGI 名保持一致,但底层的分辨率其实由 Windows 窗口决定,所以很多适配版里你填 DETECT 得到的是一块方方正正的窗口画布,分辨率往往就是 640x480。
第三个参数 path 负责指定 BGI 驱动和字体文件(.BGI、.CHR)的存放目录。现代开发收到空字符串的影响不大,但如果你自己下载过字体文件,或者运行环境总提示File not found,就得考虑把这个参数改成绝对路径,比如"C:\\Dev-Cpp\\include",注意双反斜杠转义。
初始化流程的标准姿势是:
int gd = DETECT, gm; initgraph(&gd, &gm, ""); int err = graphresult(); if (err != grOk) { printf("Graphics error: %s\n", grapherrormsg(err)); return 1; }graphresult()读取出错码,grapherrormsg()转换成可读文本。这一步很多人跳过,结果程序黑屏退出一句提示没有,根本不知道是驱动没找到还是字体文件缺失。另一个容易踩的坑是:closegraph 之后千万别再调用任何绘图函数,否则运行期直接崩,而且崩得毫无预兆。
3.2 从直线到填充多边形:颜色模型与调用顺序
BGI 的颜色模型是 0 到 15 的索引色表,0 黑色、1 蓝色、15 白色,具体值可以在 graphics.h 里看到enum定义。绘制任何图形前,先通过setcolor设定画笔颜色,也就是线条颜色;如果要做填充,再调setfillstyle设定填充模式和填充颜色。填充模式用整型常量,SOLID_FILL是实心填充,HATCH_FILL是斜线填充,颜色和画笔色不一定相同。
这里有一个新手最常犯的调用顺序错误:先画了图形,再调用 setcolor,发现颜色没变。因为 setcolor 只影响后续绘制的图形,已经画上去的内容不会被重新着色。所以标准流程永远是「先设置属性,再执行绘制」。
到多边形时,drawpoly 和 fillpoly 是一对容易混淆的函数。drawpoly 只画边框,fillpoly 做实心填充,两者都接收顶点数和坐标数组。坐标数组必须是 int 型一维数组,按[x0, y0, x1, y1, ...]顺序排。顶点数传 3 就是三角形,传 4 就是四边形,不需要额外闭合数组首尾,函数内部自动处理。
下面是一段完整的绘制小屋示例,把点、线、填充都串起来:
#include <graphics.h> int main() { int gd = DETECT, gm; initgraph(&gd, &gm, ""); // 墙体:矩形区域,用 fillpoly 传四个顶点 int wall[] = {100, 300, 300, 300, 300, 400, 100, 400}; setcolor(BLUE); setfillstyle(SOLID_FILL, LIGHTGRAY); fillpoly(4, wall); // 屋顶:三角形三个顶点 int roof[] = {80, 300, 320, 300, 200, 200}; setcolor(RED); setfillstyle(SOLID_FILL, RED); fillpoly(3, roof); // 门:用 rectangle 和 floodfill 配合 setcolor(BLACK); rectangle(180, 340, 220, 400); setfillstyle(SOLID_FILL, BROWN); floodfill(200, 360, BLACK); // 标题文字 setcolor(WHITE); settextstyle(SANS_SERIF_FONT, HORIZ_DIR, 2); outtextxy(120, 120, "HOME"); getch(); closegraph(); return 0; }这段代码里floodfill(200, 360, BLACK)是另一个容易踩坑的细节:三个参数分别是种子点坐标和边界颜色。它会从种子点开始扩散填充,直到碰到边界颜色为止。如果边界色跟填充色一致,或者边界没封闭,填充会溢到窗外。这里先画了黑色矩形边框,再以边框内的一点做种子,就能安全填进门里。颜色常量里没有 BROWN 时,可以直接填整数6,效果一样。outtextxy 最后两个参数是坐标,文字以左上角定位,字体大小由 settextstyle 的第三个参数控制。
3.3 像素读写与图像块操作:getpixel / putpixel / getimage / putimage
图形绘制并不意味着只能画几何图形,很多课程设计要求做像素级的图像处理。BGI 提供了 getpixel 和 putpixel 这一对函数,前者返回指定坐标的像素颜色值,后者往指定坐标写一个带颜色的点。用循环遍历整块矩形区域,就能实现图片反色、灰度化这类实验。
for (int y = 50; y < 100; y++) { for (int x = 50; x < 100; x++) { int color = getpixel(x, y); putpixel(x, y, 15 - color); // 16 色索引取反 } }这段代码把 50 到 100 像素见方区域的每个点取反色。原理是 16 色索引里15 - color恰好对应互补色,黑色变白色,白色变黑色。注意 getpixel 在 BGI 中是对屏幕直接采样,速度偏慢,实际项目里尽量让循环体尺寸小一点。
getimage 和 putimage 则是高价值函数,做法是把屏幕上某个矩形区域的内容截到内存缓冲区,再把这个缓冲区的内容原样贴到屏幕任意位置。这个能力是做动画不闪屏的基础。使用的内存大小有专门函数计算:imagesize(x1, y1, x2, y2)返回所需字节数,用它配合 malloc 分配缓冲区。
unsigned int size = imagesize(50, 50, 100, 100); void* buf = malloc(size); getimage(50, 50, 100, 100, buf); putimage(200, 200, buf, COPY_PUT); free(buf);COPY_PUT最简单也最常用,表示整块覆盖目标区域。其他模式还有 XOR_PUT 可以做异或叠加,用于实现拖拽移动的效果,但容易产生颜色残影,初学阶段建议只用 COPY_PUT。
4. 避坑合集:graphics.h 编译失败、黑屏闪退和中文乱码的排查路径
4.1 编译报错 graphics.h: No such file or directory,不一定是没装好
现象:代码第一行#include <graphics.h>编译直接失败,错误提示找不到头文件,但你明明已经把 graphics.h 复制到编译器目录了。
原因有三个常见可能。第一,复制时进错了目录,Dev-C++ 里系统 include 路径和 C++ 标准库路径往往并列存在,文件被放到非搜索路径的子目录。第二,Code::Blocks 用的是全局搜索目录,但工程级设置没有同步更新,编译器没把路径纳入扫描范围。第三,更隐蔽的是系统里有多个 GCC 版本,Dev-C++ 的编译器自带 include 目录和 Code::Blocks 里 MySQL 的 include 目录不一样。
解决思路:别凭感觉猜,直接在 IDE 的编译日志里查看-I参数输出了哪些路径,对比你的文件实际所在地。Dev-C++ 最简单的方法是用菜单栏「工具 > 编译选项 > 目录」复制出真实路径,然后再复制文件过去。Code::Blocks 则直接在工程上右键,进入 Build options -> Search directories,添加完确定并重建,日志里能看到路径。
4.2 链接报错 cannot find -lgraphics:库的命名规则没对上
现象:编译能过,链接阶段报cannot find -lgraphics或者ld returned 1 exit status。
原因:gcc 在链接参数里看到-lgraphics,会去搜索libgraphics.a和libgraphics.so,你的文件叫 graphics.lib,完全没有匹配上。在 Windows 下,MinGW 默认也支持直接输入绝对路径的 .a 文件,但 IDE 的图形化链接设置要走完整流程。
解决:切换到库文件所在目录,把它复制一份重命名为libgraphics.a。如果用的是 Code::Blocks,我一般直接在 Linker settings 里用 Add 按钮选择libgraphics.a文件,不写-l参数,彻底绕开命名规则问题。Dev-C++ 则改完名字后,在链接器参数栏保留-lgraphics即可。偶尔还见到一种现象:链接全过了,运行后提示找不到 DLL。那说明你下的适配版其实是动态链接封装,包里漏了对应的 DLL 文件,需要把它放到 exe 同级目录,这是另一种问题的表现。
4.3 程序跑起来窗口闪一下就消失:getch 与 closegraph 的顺序问题
现象:运行程序,屏幕上闪过一个窗口立刻消失,完全看不清画出来的东西。
原因:绘图完成后,main 函数立刻执行到 return 0,图形窗口随进程结束被系统销毁。BGI 不像 Qt 那样有事件循环可以挂住窗口,程序一旦退出所有画面都消失。另外还有一种情况:getch()用错了位置,放在了initgraph之前,相当于在没进入图形模式前就等待按键,等于白等。
解决:确保getch();紧跟在所有绘图语句之后、closegraph();之前。closegraph关闭图形模式的瞬间绘图区被还原,所以如果有人写完画图就 closegraph 再 getch,那你看到的是黑屏也不是画面。标准顺序是:绘图逻辑 - getch - closegraph。想要连按任意键退出后再有个停顿,可以在 closegraph 之后再来一个 getch,但那种写法画布已经没了,只对等待窗口有意义。
4.4 outtextxy 中文乱码:点阵字体的边界
现象:outtextxy 输出中文,屏幕上显示一堆乱七八糟的符号,或者干脆什么都不显示。
原因:BGI 原生字体是 8x8 点阵字体,字符编码表里只包含英文、数字和符号。中文是双字节编码,点阵字库没有对应的字形表。WinBGIm 适配版在字体上做了增强,但 settextstyle 指定的几套内置字体仍然不是中文字体。
解决:三个思路。第一,图形上尽量用英文标题和标注,这也是 BGI 程序的老传统。第二,非用中文不可时,可以用putimage先把中文截图做成资源贴上去,虽然麻烦但效果可控。第三,WinBGIm 环境下尝试把字体设置成DEFAULT_FONT,同时配合系统自带的 TTF 中文字体名去调用扩展接口。需要说明的是,第三点依赖适配版的实现,有些版本的 graphics.h 并没有暴露相关函数,这种时候就不要跟它较劲,回到第一、第二种方案。从实践角度看,课程设计界面里英文标题完全够用,项目展示时反而更不容易乱码。
4.5 BGI Error: Graphics not initialized 或花屏:关闭顺序与模式不匹配
现象:程序运行时弹出BGI Error: Graphics not initialized,或者初始画布分辨率异常、花屏。
原因:最常见的是代码里调用了closegraph()之后,又执行了某个绘图函数。比如循环里先 closegraph 再 cirle,第一次循环就崩。另一个原因是 initgraph 参数里的模式值超出当前驱动支持的范围,比如 VGA 驱动下强行指定 SVGA 模式,适配层来不及换算,导致画布尺寸错乱。
解决:严格遵守「initgraph 一次初始化、closegraph 一次收尾」的结构,把 closegraph 放在 main 函数所有绘图逻辑之后。如果程序里有多个退出路径,用 goto 或者 return 前统一收尾。碰到分辨率问题,把gm的模式设置改成DETECT,让图形库自己选,画布尺寸不满意再用setviewport做逻辑裁剪,而不是去手动指定一个不兼容的模式。
顺手整理一个快查表,遇到问题先对一下:
| 现象 | 优先排查项 | 常见解决 |
|---|---|---|
| 编译找不到头文件 | include 路径 vs 文件实际目录 | 用编译日志查 -I 参数 |
| 链接找不到库 | 库文件名是否符合 libxxx.a | 重命名或改用绝对路径 |
| 运行闪退无画面 | getch / closegraph 顺序 | 先 getch 后 closegraph |
| 中文乱码 | 字体编码不支持 | 换英文标注或用图像贴字 |
| 初始化失败 | graphresult 错误码 | 打印 grapherrormsg 定位 |
5. 让画面动起来:双缓冲思路、键盘鼠标与一个小球弹跳动画
图形库教学走到最后,几乎所有人都会尝试做动画。BGI 做动画的原理很简单:擦掉旧画面、在新位置画新画面、重复循环。难点在于擦除方式,如果直接清屏重画,屏幕会明显闪烁,因为人眼能看到背景被清掉的那一帧。
我的做法是先截背景再画球,每次移动时用 putimage 把背景贴回去、在新位置重新画球,避免全屏 clear。这就是最朴素的双缓冲思路。下面是一个完整的小球弹跳动画:
#include <graphics.h> #include <conio.h> #include <stdlib.h> int main() { int gd = DETECT, gm; initgraph(&gd, &gm, ""); int x = 50, y = 50; // 初始位置 int dx = 3, dy = 3; // 每帧位移 int r = 15; // 半径 // 预分配背景缓冲区 unsigned int size = imagesize(x - r, y - r, x + r, y + r); void* buf = malloc(size); while (!kbhit()) { // 1. 截取当前位置的背景 getimage(x - r, y - r, x + r, y + r, buf); // 2. 按边界更新坐标 x += dx; y += dy; if (x <= r || x >= getmaxx() - r) dx = -dx; if (y <= r || y >= getmaxy() - r) dy = -dy; // 3. 画球 setcolor(YELLOW); setfillstyle(SOLID_FILL, YELLOW); fillellipse(x, y, r, r); // 4. 暂停约 10ms delay(10); // 5. 恢复背景(把球擦掉) putimage(x - r, y - r, buf, COPY_PUT); } free(buf); closegraph(); return 0; }这段代码里,缓冲区是提前按初始位置算好的,实际上每次移动后球的位置大小一致,缓冲区尺寸始终够用。getimage截的是上一步的球所在区域,putimage把它原样贴回,等于把旧球连同背景一起还原,再进入下一轮循环。delay(10)控制帧率,数值越小动画越流畅,但太小会造成 CPU 占用过高,10 到 20 毫秒是控制台动画的常见取值。
kbhit()来自 conio.h,它不阻塞程序,循环里每帧检查一次是否按键按下。想要响应鼠标则可以配合mousemsg(),读取mousex()和mousey()获得当前坐标,用法相似。这类交互不复杂,关键在于把鼠标采样放在循环开头,别让事件挤在动画更新之后。
说实话,我第一次做动画时图省事,直接在循环里调用cleardevice()全屏清空再重画,结果窗口边缘闪得眼睛疼,连续跑了三秒我就受不了。从那以后,但凡做连续绘图,我都会先把背景截到 image 缓冲区里,用 putimage 做定点恢复,再更新画面,这一条工艺路线已经成了我的默认习惯。BGI 虽然老,但这个套路放现在写小游戏、模拟器仍然实用。希望这套配置和调试路径能帮你在自己的 C++ graphics 图形绘制项目上少走几趟弯路。
本文还有配套的精品资源,点击获取