嵌入式UI框架-抗锯齿画圆弧算法
最近在手搓一个GUI框架,做完了抗锯齿画圆弧算法,支持任意宽度,任意角度,平头/圆头。纯C实现,写完来记录一下,帮助需要的人了解光栅化圆弧的原理。
博客大部分也是由AI生成,主要还是懒。需要深入了解算法和获取源码联系作者VX17768556499 QQ2907487307
先来看下效果,背景灰色

改变不透明度


改变宽度


放大看下抗锯齿细节

IPGUI绘图模块实操指南:圆形、圆环、弧线怎么画才好看?
一、先搞懂:这个模块能画啥?
这份代码是 IPGUI 库的「基础绘图工具包」,核心能实现 3 种常用图形,直接对应 UI 开发中的高频场景:
|
功能函数 |
能画什么 |
实际用途 |
|
ipgui_circle_ras |
圆形(含实心圆) |
按钮、指示灯、头像框 |
|
ipgui_ring_ras |
圆环(空心圆 / 环形) |
进度条、加载动画、仪表盘边框 |
|
ipgui_arc_ras |
弧线(带角度、可圆角) |
扇形图表、滑动条轨道、进度环 |
核心优势很实在:
- 自带「抗锯齿」:画出来的线条 / 圆弧不毛糙,边缘顺滑;
- 支持透明度:可以叠加在其他元素上,不会显得生硬;
- 适配屏幕裁剪:超出显示区域的部分自动不画,不浪费性能。
二、核心逻辑:用代码画图形的底层逻辑(超通俗版)
很多人觉得 “代码画图形” 很神秘,其实本质就 3 步:
- 确定 “画哪里”:算出图形在屏幕上的范围(比如圆形的左上角到右下角);
- 遍历 “每个像素”:逐个检查范围内的像素,判断是否属于要画的图形;
- 上色:属于图形的像素,按设定的颜色和透明度染色。
这部分代码的聪明之处,就是在 “判断像素是否属于图形” 上做了优化 —— 不用复杂公式,用「距离平方」「斜率对比」快速判断,还能通过 “透明度渐变” 实现抗锯齿(比如边缘像素半透明,看起来就不锯齿了)。
三、逐个拆解:3 个核心函数怎么用 + 怎么工作
3.1 画圆形:ipgui_circle_ras
3.1.1 直接能用的代码示例
|
// 需求:在屏幕(100,200)位置画一个半径30的蓝色实心圆,透明度80% ipgui_tile_t *tile = ipgui_get_current_tile(); // 获取当前绘图区域 ipgui_point_t center = {100, 200}; // 圆心坐标 ipgui_coord_t radius = 30; // 半径 ipgui_color_t blue = IPGUI_COLOR_BLUE; // 颜色(库自带的颜色常量) unsigned char alpha = 80; // 透明度(0=全透明,255=不透明) // 调用函数画图 ipgui_circle_ras(tile, center, radius, blue, alpha); |
3.1.2 它是怎么工作的?(不用懂公式,看逻辑)
这个函数其实是 “偷懒” 实现的 —— 它没有直接画圆,而是画了一个「圆角矩形」:
- 把圆形的范围当成一个正方形(边长 = 2 * 半径);
- 给这个正方形的 4 个角设置 “圆角半径 = 圆形半径”;
- 填充整个正方形,就得到了圆形。
小提醒:代码里有个注释 “画出来的圆更像一个圆角矩形,需要优化一下 shape 参数”—— 如果发现画出来的圆有点 “方”,可以把shape.radius改小一点(比如原来的radius+1改成radius)。
3.2 画圆环:ipgui_ring_ras
圆环就是 “空心的圆”,比如时钟的边框、加载动画的外圈。
3.2.1 直接能用的代码示例
|
// 需求:画一个中心在(200,200)、外半径40、内半径30的红色圆环,透明度128 ipgui_ring_ras_attr_t ring_attr; // 圆环的配置参数 // 填充配置 ring_attr.center = (ipgui_point_t){200, 200}; // 圆心 ring_attr.er = 40; // 外半径(er=outer radius) ring_attr.ir = 30; // 内半径(ir=inner radius) ring_attr.color = IPGUI_COLOR_RED; // 颜色 ring_attr.alpha = 128; // 透明度 // 调用函数画图 ipgui_ring_ras(ipgui_get_current_tile(), &ring_attr); |
3.2.2 关键逻辑:怎么判断 “像素属于圆环”?
圆环的核心是 “像素在内外圆之间”,代码里用了个简单方法:
- 先算像素到圆心的「距离平方」(比如像素 (x,y),距离平方 =(x - 圆心 x)² +(y - 圆心 y)²);
- 外圆半径平方 = 40²=1600,内圆半径平方 = 30²=900;
- 如果像素的距离平方在 900~1600 之间,就是圆环的部分,上色;
- 边缘部分(比如距离平方 = 1605,刚超出外圆),给半透明,实现抗锯齿。
为什么用 “距离平方” 而不是 “距离”?—— 因为开平方计算慢,直接用平方对比,性能能快一倍,对嵌入式设备特别友好。
3.2.3 避坑指南
|
问题现象 |
原因 |
解决办法 |
|
圆环画不出来 |
内半径≥外半径,或透明度 < 4 |
确保 ir 4 |
|
圆环变成实心圆 |
内半径设为 0 |
想画空心就把 ir 设为≥1,想实心直接用 ipgui_circle_ras |
|
圆环边缘毛糙 |
没开抗锯齿(代码默认开了) |
不用改,代码里的 inv_inner/inv_outer 就是抗锯齿参数 |
3.3 画弧线:ipgui_arc_ras(最强大也最常用)
弧线是 “圆的一部分”,支持设定起始角度、结束角度,还能给两端加圆角,是做进度条、仪表盘的核心函数。
3.3.1 直接能用的代码示例(画一个 3/4 圆弧进度条)
|
// 需求:画一个中心在(300,300)、外半径50、内半径45的绿色弧线,角度从0°到270°,两端圆角 ipgui_arc_ras_attr_t arc_attr; // 填充配置 arc_attr.cx = 300; // 圆心x arc_attr.cy = 300; // 圆心y arc_attr.er = 50; // 外半径 arc_attr.ir = 45; // 内半径 arc_attr.angle_start = 0; // 起始角度(0°=正右方) arc_attr.angle_end = 270; // 结束角度(270°=正下方) arc_attr.color = IPGUI_COLOR_GREEN; // 颜色 arc_attr.alpha = 200; // 透明度 // 调用函数:最后一个参数1=两端圆角,0=直角 ipgui_arc_ras(ipgui_get_current_tile(), NULL, &arc_attr, 1); |
3.3.2 角度怎么算?(一张图看懂)
代码里的角度是「顺时针计算」的,和我们平时的数学角度有点不一样:
- 0°:正右方(比如时钟 3 点钟方向);
- 90°:正下方(时钟 6 点钟方向);
- 180°:正左方(时钟 9 点钟方向);
- 270°:正上方(时钟 12 点钟方向);
- 360°=0°:一圈回到起点。
如果想画 “从 12 点到 3 点” 的弧线,起始角度就是 270°,结束角度是 0°。
3.3.3 核心逻辑:怎么画 “带角度的弧线”?
- 先把圆分成 4 个象限(就像坐标系的 4 个象限),逐个象限画,避免漏画;
- 用「斜率对比」判断像素是否在设定的角度范围内(比如 0° 到 90° 之间的像素,斜率要满足一定条件);
- 同时判断像素是否在内外半径之间(和圆环逻辑一样);
- 两端圆角:在弧线的起点和终点,各画一个小半圆,让边缘更顺滑。
3.3.4 高级用法:画一个动态进度环
|
// 需求:进度从0%到100%,对应的弧线角度从0°到360°,实时更新 void draw_progress_ring(int progress) { ipgui_arc_ras_attr_t arc_attr; arc_attr.cx = 200; arc_attr.cy = 200; arc_attr.er = 50; arc_attr.ir = 45; arc_attr.color = IPGUI_COLOR_GREEN; arc_attr.alpha = 255;
// 进度转角度:progress=0→0°,progress=100→360° arc_attr.angle_start = 0; arc_attr.angle_end = (progress * 360) / 100;
// 画弧线 ipgui_arc_ras(ipgui_get_current_tile(), NULL, &arc_attr, 1); } // 调用:实时更新进度(比如每100ms加1%) for (int progress = 0; progress ; progress++) { draw_progress_ring(progress); ipgui_delay_ms(100); // 延时100ms } |
四、抗锯齿:为什么别人的图形边缘顺滑?
很多人画图形会遇到 “边缘锯齿”,这份代码自带的抗锯齿逻辑很简单,核心就是 “边缘像素半透明”:
- 比如画一个圆,最边缘的像素,有的只占了半个像素的面积;
- 代码会计算这个像素 “属于图形的比例”,比如占 50%,就给 50% 的透明度;
- 人眼看起来,半透明的边缘和背景融合,就不会有明显的锯齿。
关键代码在ipgui_ring_ras和ipgui_arc_ras里的cover变量:
- cover=255:完全属于图形,不透明;
- cover=128:一半属于图形,半透明;
- cover=0:不属于图形,不上色。
如果觉得抗锯齿效果不够好,可以调整inv_inner和inv_outer参数(数值越大,抗锯齿越明显)。
五、实战案例:用这 3 个函数搭一个简易仪表盘
需求:
- 外框:红色圆环(外半径 60,内半径 55);
- 进度条:绿色弧线(角度 0° 到 225°,外半径 58,内半径 57);
- 中心点:蓝色实心圆(半径 5)。
完整代码:
|
void draw_dashboard() { ipgui_tile_t *tile = ipgui_get_current_tile(); ipgui_point_t center = {300, 300}; // 仪表盘中心 // 1. 画外框圆环(红色) ipgui_ring_ras_attr_t ring_attr; ring_attr.center = center; ring_attr.er = 60; ring_attr.ir = 55; ring_attr.color = IPGUI_COLOR_RED; ring_attr.alpha = 200; ipgui_ring_ras(tile, &ring_attr); // 2. 画进度弧线(绿色,0°到225°,两端圆角) ipgui_arc_ras_attr_t arc_attr; arc_attr.cx = center.x; arc_attr.cy = center.y; arc_attr.er = 58; arc_attr.ir = 57; arc_attr.angle_start = 0; arc_attr.angle_end = 225; arc_attr.color = IPGUI_COLOR_GREEN; arc_attr.alpha = 255; ipgui_arc_ras(tile, NULL, &arc_attr, 1); // 3. 画中心点(蓝色实心圆) ipgui_circle_ras(tile, center, 5, IPGUI_COLOR_BLUE, 255); } // 主函数调用 int main() { ipgui_init(); // 初始化IPGUI库 while (1) { ipgui_tile_clear(ipgui_get_current_tile()); // 清空屏幕 draw_dashboard(); // 画仪表盘 ipgui_tile_flush(ipgui_get_current_tile()); // 刷新显示 ipgui_delay_ms(50); } return 0; } |
效果示意图:
|
绿色弧线(225°) ┌─────────────┐ │ │ │ │ 红色圆环外框 │ │ 中心点(蓝色) │ │ │ │ └─────────────┘ 0°方向 |
六、常见问题排查(踩坑总结)
|
问题现象 |
排查步骤 |
解决办法 |
|
图形画不出来 |
1. 检查坐标是否超出屏幕范围;2. 透明度是否;3. 半径是否为 0 |
1. 调整圆心坐标和半径;2. alpha≥4;3. er≥1 |
|
图形边缘有锯齿 |
1. 是不是没开抗锯齿;2. 内外半径差距太小 |
1. 代码默认开抗锯齿,不用改;2. 内外半径差距≥2 |
|
弧线角度不对 |
1. 角度是否搞反(起始 > 结束);2. 角度是否超 360° |
1. 结束角度如果小于起始,自动加 360°;2. 超 360° 会自动当成一圈 |
|
图形重叠有残影 |
1. 每次画图前是否清空屏幕;2. 透明度是否过高 |
1. 调用 ipgui_tile_clear 清空;2. 降低 alpha 值 |
|
圆环变成实心 |
内半径 ir 是否设为 0 |
想画空心,ir 至少设为 1;想实心用 ipgui_circle_ras |
七、小技巧:让图形更好看的 3 个实用调整
- 调整内外半径差距:画弧线时,内外半径差距 1~2 像素,看起来像 “细线”;差距 5~10 像素,看起来像 “宽条”;
- 透明度叠加:比如用两个不同透明度的弧线叠加,能做出渐变效果;
- 圆角 vs 直角:画进度条用圆角(最后一个参数设 1),画分割线用直角(设 0),更贴合 UI 风格。
(注:文档部分内容可能由 AI 生成)

浙公网安备 33010602011771号