Qt二维图形绘制
当需要自定义控件,特别是风格和原生的控件完全不同时,往往需要使用Qt的二维图形引擎进行绘制。Qt的二维图形引擎是基于QPainter类的。QPainter类可以绘制各自二维图形,图像,设置文字。分别借助它的三种绘制工具:
- QPen,QPen画笔用于绘制点、线以及拓展出来的各自几何形状,它主要是勾勒边缘。
- QBrush,QBrush画刷用于对闭合区域进行颜色填充,或者纹理填充(用图片)达到绘制面的效果。
- QFont,QFont用于渲染文字。
QPainter能在任意继承了QPaintDevice类的子类上进行绘制,一般通过重写虚函数virtual void paintEvent(QPaintEvent *event);
来进行绘制。更高级的三维图形的绘制往往通过OpenGL来实现,QtOpenGL对OpenGL进行了简化封装,这里讨论的是二维图形绘制,先不管三维的。
坐标系
理解二维图形绘制渲染前,往往需要先搞清楚一个概念:坐标系。Qt的二维坐标系的原点在屏幕的左上角,从左至右为x轴,从上至下为y轴,单位为像素点。
根据坐标系,可以对二维图形进行缩放,旋转,转换等操作,后面会讨论到。
使用QPen绘制点、线、几何图形
先创建一个QWidget的窗口,然后重写其paintEvent函数。上面已介绍,QPainter包含三种绘制工具:QPen,QBrush,QFont应对不同的绘制需求。QPen用来画点,线,勾勒图形边缘。QPen又包含一些参数,分别是:
-
- 颜色
-
- 线条粗细(几个像素宽度)
-
- 线条风格
-
- 线条端点风格
-
- 线条连接风格
其中1和2都比较好理解,3、4、5需要根据实际效果理解。QPainter设置好QPen属性后,就可以调用drawXXX系列函数来绘制相关的图形了。
线条风格:
线条端点风格:
乍一看,FlatCap和SquareCap没有区别,实际如下图所示:
SquareCap风格比FlatCap在端点处多出一个矩形的衔接
线条连接风格:
非闭合二维图形绘制
绘制单点,多点
painter.setPen(QPen(Qt::red, 5)); // 单点 painter.drawPoint(10, 10); painter.setPen(QPen(Qt::green, 5)); // 多点 painter.drawPoints(QVector<QPoint>() << QPoint(50, 50) << QPoint(100, 75) << QPoint(150, 43));
尽管它们都有不同的重载函数,但是关键参数都是点的坐标(x,y)。
绘制单线条,多线条
painter.setPen(QPen(Qt::red, 2)); // 单线条 painter.drawLine(10, 10, 100, 10); painter.setPen(QPen(Qt::green, 2)); // 多线条 painter.drawLines(QVector<QLine>() << QLine(10, 30, 100, 30) << QLine(10, 50, 100, 70));
尽管它们都有不同的重载函数,但是关键参数都是线条的起始坐标(x1,y1)和结束坐标(x2,y2)。
绘制折线
painter.setPen(QPen(Qt::red, 2)); painter.drawPolyline(QVector<QPoint>() << QPoint(34, 11) << QPoint(160, 100) << QPoint(210, 200));
折线就是多个坐标点,每相邻的一组连接成一条线,最后一个点和第一个点不连接,不构成封闭图形。
绘制弧
painter.setPen(QPen(Qt::red, 2)); // 逆时针画弧 painter.drawArc(50, 50, 150, 100, 0, 240 * 16); painter.setPen(QPen(Qt::green, 2)); // 顺时针画弧 painter.drawArc(50, 200, 150, 100, 0, -240 * 16);
弧线的绘制理解起来是稍微复杂一点的,并且弧长的控制也是较特殊的。取其一个重载函数解释:
void QPainter::drawArc(int x, int y, int w, int h, int a, int alen)
其中前4个参数,是在描述一个矩形,参数a
,是指起始弧度,参数alen
是指旋转弧度,若alen
为负数,表示弧的延伸方向为反向的,默认为逆时针。
以下面的代码绘制的图形来理解:
// 绘制一个虚线构成的矩形 painter.setPen(QPen(Qt::green, 1, Qt::DashLine)); painter.drawRect(50, 50, 150, 100); // 绘制弧线,注意!它们前4个参数一样的 painter.setPen(QPen(Qt::red, 2)); painter.drawArc(50, 50, 150, 100, 0, 240 * 16);
实际效果:
可以看到前4个参数指定的矩形里面填进去一个内切的椭圆(如果w = h,即矩形为正方形,那么里面的椭圆就是一个圆),弧度的起始弧度0°位于最右侧。
Qt绘制弧线时,弧度数单位为1/16°。
闭合二维图形
接下来绘制的图形都是形成一个闭合区域,闭合区域形成后,就会引入QPainter的第二个工具,QBrush,画刷,一旦设置了它,遇到闭合图形,就会往闭合图形里面按照QBrush设置的参数,进行填充。非闭合图形是没有效果的。
画刷填充的风格也有多种:
矩形
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); painter.drawRect(10, 10, 120, 80);
圆角矩形
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); painter.drawRoundedRect(50, 50, 300, 200, 35, 10);
多边形
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); // 3个点,构成一个三角形 painter.drawPolygon(QVector<QPoint>() << QPoint(34, 11) << QPoint(160, 10) << QPoint(210, 180));
椭圆
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); painter.drawEllipse(10, 10, 120, 80);
对于该重载函数:
void QPainter::drawEllipse(int x, int y, int w, int h)
前面画弧线时已提到过,画椭圆实际就是一个矩形的左上角顶点坐标(x,y)和宽度w,高度h,然后里面画一个内切的椭圆型,当w=h时就是一个圆。
弦形
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); painter.drawChord(10, 10, 120, 80, 20 * 16, 140 * 16);
和画弧线类似,弓形只不过是起点和终点用直线连接起来了,所以它们的参数含义都是完全一样的。
饼形
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern)); painter.setPen(QPen(Qt::red, 2)); painter.drawPie(10, 10, 120, 80, 40 * 16, 100 * 16);
它的参数含义依然和弧线,弦形一样。至于为什么叫饼形,可能是像切出来的一块披萨。
路径
待补充....
自定义画刷风格
画刷填充除了预置的一些风格,还可以自定义渐变风格。渐变风格有三种模式:线性渐变,辐射渐变,锥形渐变。
线性渐变
// 线性渐变, 参数为渐变基准线的起始坐标和结束坐标 QLinearGradient gradient(0, 50, 300, 50); // 插入颜色 gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(0.5, Qt::green); gradient.setColorAt(1.0, Qt::blue); painter.setBrush(gradient); // 画出区域 painter.setPen(QPen(Qt::black, 1, Qt::SolidLine)); painter.drawRect(0, 0, 300, 100); // 为了方便理解,画出渐变基准线 painter.setPen(QPen(Qt::yellow, 1, Qt::DashLine)); painter.drawLine(0, 50, 300, 50);
对于QLinearGradient
的构造函数:
QLinearGradient(); QLinearGradient(const QPointF &start, const QPointF &finalStop); QLinearGradient(qreal xStart, qreal yStart, qreal xFinalStop, qreal yFinalStop);
它主要是指定渐变基准线的起始坐标和结束坐标,色彩的梯度渐变就按照这条基准线延伸。
void setColorAt(qreal pos, const QColor &color);
方法在基准线指定的位置可以插入一些渐变起始颜色,然后由算法将相邻位置的颜色自动完成梯度渐变,这个位置pos
是一个0.0 - 1.0的数值,指的是基准线百分比位置。如上面的代码,0.0和1.0插入了red和blue,中间50%位置处插入了一个green,所以可以看到正绿色位于正中间。
填充区域中和基准线垂直的线条的颜色都是一样的,所以并不是说基准线必须位于填充区域的中间位置。例如代码所示:
// 线性渐变, 参数为渐变基准线的起始坐标和结束坐标 QLinearGradient gradient(0, 10, 300, 10); // 插入颜色 gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(0.5, Qt::green); gradient.setColorAt(1.0, Qt::blue); painter.setBrush(gradient); // 画出区域 painter.setPen(QPen(Qt::black, 1, Qt::SolidLine)); painter.drawRect(0, 0, 300, 100); // 为了方便理解,画出渐变基准线 painter.setPen(QPen(Qt::yellow, 1, Qt::DashLine)); painter.drawLine(0, 10, 300, 10);
并且一旦超出基准线的范围,就不再有渐变效果,而是按照最后一个颜色填充。
// 线性渐变, 参数为渐变基准线的起始坐标和结束坐标 QLinearGradient gradient(0, 50, 300, 50); // 插入颜色 gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(0.5, Qt::green); gradient.setColorAt(1.0, Qt::blue); painter.setBrush(gradient); // 画出区域 painter.setPen(QPen(Qt::black, 1, Qt::SolidLine)); painter.drawRect(0, 0, 400, 100); // 为了方便理解,画出渐变基准线 painter.setPen(QPen(Qt::yellow, 1, Qt::DashLine)); painter.drawLine(0, 50, 300, 50);
锥形渐变
QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QConicalGradient gradient(100 + 240 / 2, 100 + 240 / 2, 45); gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(0.5, Qt::green); gradient.setColorAt(1.0, Qt::blue); painter.setBrush(gradient); painter.setPen(Qt::transparent); painter.drawEllipse(100, 100, 240, 240); QConicalGradient gradient1(350 + 240 / 2, 100 + 240 / 2, 45); gradient1.setColorAt(0.0, Qt::red); gradient1.setColorAt(0.5, Qt::green); gradient1.setColorAt(1.0, Qt::blue); painter.setBrush(gradient1); painter.drawRect(100 + 240 + 10, 100, 240, 240);
QConicalGradient的构造函数
QConicalGradient::QConicalGradient(qreal cx, qreal cy, qreal angle)
的第一个参数是锥形渐变的圆心,第二个参数是起始角度,Qt文档里面说它的值在0-360度之间,实际可用是任意值,包括整数,浮点数,正数,负数。
它的0°位于圆心向右延伸的水平方向。角度的旋转方向总是逆时针的。锥形渐变setColorAt
的第一个参数pos
是指0°-360°的百分比角度。
同角度上的颜色值是一样的,颜色梯度随着旋转角度渐变。
径向渐变
径向渐变分为两种,简单径向渐变和扩展径向渐变。先从简单的来了解。
简单径向渐变
int cx = 150; int cy = 150; int radius = 150; //int fx = 100; //int fy = 100; QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QRadialGradient gradient(cx, cy, radius); gradient.setColorAt(0.0, Qt::red); gradient.setColorAt(0.5, Qt::green); gradient.setColorAt(1.0, Qt::blue); painter.setBrush(gradient); painter.setPen(Qt::transparent); // 绘制一个圆心,半径和径向渐变参数一致的圆,展示径向渐变效果 painter.drawEllipse(0, 0, radius * 2, radius * 2);
所谓径向渐变,就是沿着半径的方向进行颜色渐变。其构造函数都是围绕下面这个构造函数的重载变种
QRadialGradient(qreal cx, qreal cy, qreal radius, qreal fx, qreal fy);
其中cx,cy
确定了圆心,radius
为圆的半径,超出半径时,不再有渐变效果,而是保持最后那个颜色。fx,fy
为焦点,为了便于理解,从容易到难,上面代码中暂时忽略了此参数。也就是对应构造函数:
QRadialGradient::QRadialGradient(qreal cx, qreal cy, qreal radius)
实际该构造函数里面会把cx,cy
作为焦点,也就是圆心和焦点重合的情况。如果从圆心拉出一根半径线,你会发现渐变沿着半径线梯度变化,setColorAt
函数的第一个参数pos
对应只的就是半径的百分比。
也就是说此时,以圆心为中心的圆环上的颜色是一样的。
再看焦点和圆心不重叠的例子:
int cx = 150; int cy = 150; int radius = 100; int ellipseRadius = 150; int fx = 100; int fy = 100; QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QRadialGradient gradient(cx, cy, radius, fx, fy); gradient.setColorAt(0.0, Qt::white); gradient.setColorAt(1.0, Qt::black); painter.setBrush(gradient); painter.setPen(Qt::transparent); painter.drawEllipse(0, 0, ellipseRadius * 2, ellipseRadius * 2);
为了方便理解,这里特意把颜色插值设置为黑白两种,并且把渐变半径设置为小于画出来的圆的半径(这样就看得出渐变范围的轮廓,我们已知渐变效果只在渐变半径内产生,超出的区域颜色维持不变)。看到上面的图片,很容易让人联想到3D中的点光源光照效果,不过我觉得好像不是一回事。
再结合下面的示意图,我相信应该可以理解焦点和圆心非重叠时的渐变是如何进行的了。
圆心和半径决定渐变的范围,焦点决定渐变的起点,此时就不是简单的画圆环了,而是朝各个方向引出一根径线,直到圆心和半径限定的渐变范围这个圆圈上。此时setColorAt
的第一个参数pos
就是当前径线长度的百分比了。
扩展径向渐变
扩展径向渐变就是在简单径向渐变的基础上,给焦点增加了一个额外的半径,此时焦点不再是一个点,而是一个圆。
int cx = 150; int cy = 150; int radius = 100; int ellipseRadius = 150; int fx = 120; int fy = 120; int fRadius = 20; QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); QRadialGradient gradient(cx, cy, radius, fx, fy, fRadius); gradient.setColorAt(0.0, Qt::white); gradient.setColorAt(1.0, Qt::black); painter.setBrush(gradient); painter.setPen(Qt::transparent); painter.drawEllipse(0, 0, ellipseRadius * 2, ellipseRadius * 2);
示意图也和简单径向渐变类似:
焦点径线的起点位于焦点圆周上,然后向中心圆上延伸。
扩散方式
对于渐变填充,我们注意到线性渐变QLinearGradient
和径向渐变QRadialGradient
都提到了渐变范围,也就是渐变效果只在范围内产生渐变,超出的地方维持原来的颜色。
而对于锥形渐变QConicalGradient
,由于它是按照角度来的,所以没这个概念。实际上,超出渐变范围的区域的填充颜色是受一个属性控制的,就是扩散方式Spread
。
扩散方式分为3种,由void QGradient::setSpread(Spread aspread)
方法进行设置。
三种方式分别是:
PadSpread
,这是默认的方式,效果就是维持最后的颜色。ReflectSpread
,反射扩散,就是以范围长度,和前面那段镜像渐变。RepeatSpread
,重复扩散,就是以范围长度,反复进行渐变。
下面是示意图,分别对应上面三种扩散方式
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?