一文搞懂Qt中的颜色渐变(QGradient Class)
1, 快速开始!
- Qt中与颜色渐变有关的类是
QGradient
- 其中它又有三个子类:
QLinearGradient
、QRadialGradient
、QConicalGradient
分别对应三种具体的颜色渐变(后面挨个介绍)。
- 所以它们的关系是:
- Qt中颜色渐变是怎么用的?它作为一种填充(fill)方式使用,比如说之前我们可以用红色、绿色这些纯色填充一个矩形,现在我们可以用一个渐变颜色(更形象的称呼是色带)来填充,从而可以绘制出更有趣的控件!
- 先了解一下填充:有两个绕不开的东西——画笔(QPen)和画刷(QBrush)。下面来看一小段代码:
- 这里仅需要明确勾勒矩形线条时使用画笔,填充矩形内部时使用画刷。
- 因为画刷控制着填充,渐变颜色在Qt中以填充方式使用,所以需要有一种创建渐变颜色画刷的方法。
QBrush
有一个以QGradient
为唯一参数的构造函数:
QBrush(const QGradient &gradient)
- 所以想绘制一个有渐变效果的矩形可以这么写:
- 上面的例子中,我们保持画笔不变,使用
QLinearGradient
类型的颜色渐变创建了一个画刷。结果是一个很漂亮的线性颜色渐变,它从矩形左上角的白色过渡到矩形右下角的黑色。
- 让我们更进一步。。。
2, 颜色渐变
- 颜色渐变表示我们需要将一个渐变颜色集填充到一个区域中。
- 要获得一个渐变颜色集有一种简单的方式:
Color1
过渡到Color2
过程中的所有颜色所构成的集合。上面的例子我们通过指定Color1
为白色,Color2
为黑色得到一个渐变颜色集:
QLinearGradient linearGradient = ...
linearGradient.setColorAt(0, Qt::white);
^^^^^^^^^
linearGradient.setColorAt(1, Qt::black);
^^^^^^^^^
- 接下来的问题是当填充一个区域时我们还需要一个填充方向,比如是从上到下?从左到右?还是从点A到点B。
- 填充方向的指定方式不同产生了三种不同的颜色渐变类型:线性(
QLinearGradient
),径向(QRadialGradient
)和锥向(QConicalGradient
)。
2。1, 线性颜色渐变(QLinearGradient
Class)
- 如你所料,通过指定一个起点(
start
)和一个结束点(finalStop
)来确定一个线性填充方向。
QLinearGradient(qreal x1, qreal y1, qreal x2, qreal y2)
QLinearGradient(const QPointF &start, const QPointF &finalStop)
- 上面的例子中就是以矩形的左上角(
start
)到矩形的右下角(finalStop
)作为渐变填充方向。
- 所以如果我们想要从上到下填充一个矩形可以用下面这段代码:
QLinearGradient linearGradient(QPointF(0, -200), QPointF(0, 200));
linearGradient.setColorAt(0, Qt::white);
linearGradient.setColorAt(1, Qt::black);
QBrush brush(linearGradient);
painter->setBrush(brush);
painter->drawRect(-100, -200, 200, 400);
QPen pen;
pen.setColor(Qt::red);
painter->setPen(pen);
painter->drawLine(-20, 0, 20, 0);
painter->drawLine(0, 20, 0, -20);
~~~~~~~~~~~~~~
linearGradient.setColorAt(0, Qt::white);
^
linearGradient.setColorAt(1, Qt::black);
^
- 参数
position
的范围是[0,1]
,前面已经知道start
与finalStop
确定出一条带方向的线段,而参数position
相当于一个比例参数,它在这条线段上标定一个位置,表示在这个位置处达到后一个参数所代表的颜色值。
达到该颜色的位置 = start + position * (finalStop - start)
- 上图是拿黑色达到的位置为例,白色到达(开始)的位置也是一样。
- 当position等于0或1时,达到颜色的位置分别位于两个端点处,所以上面两行代码的意思是:在start处为纯白,在finalStop处为纯黑。
- 这就是线性渐变的全部。
- 线性渐变用起来非常简单,但它所有的概念也都适用于后面要说的两种渐变,所以说了这么多。。。
2。2, 锥向颜色渐变(QConicalGradient
Class)
- 锥向渐变几乎和线性渐变一样简单,所以把它作为第二个介绍。
- 锥向渐变通过一个中心点(
center
)和一个角度(angle
)来确定填充方向。
QConicalGradient(qreal cx, qreal cy, qreal angle)
QConicalGradient(const QPointF ¢er, qreal angle)
QConicalGradient conicalGradient(QPointF(0, 0), 45);
conicalGradient.setColorAt(0, Qt::white);
conicalGradient.setColorAt(1, Qt::black);
QBrush brush(conicalGradient);
painter->setBrush(brush);
painter->drawRect(-100, -100, 200, 200);
QPen pen;
pen.setColor(Qt::red);
pen.setWidth(1);
painter->setPen(pen);
painter->drawLine(-20, 0, 20, 0);
painter->drawLine(0, 20, 0, -20);
- 使用极坐标系来看待锥向渐变的构造过程也许更自然。
- 提供一个点(
center
)和一个角度(angle
)可以确定出一条射线,这相当于给出了一条起始线,给定的Color1
会从这条线开始,按逆时针方向过渡到Color2
。
- 我们在提供颜色的同时仍然提供了一个位置(
position
)参数,它的含义与线性渐变中的一样,不过在锥向渐变中把它理解成角度的比例而非距离的比例或许更好:
达到该颜色的位置 = angle + position * 360°
- 锥向渐变似乎比它看起来更简单,这一点在后面会有体现,go on。。。
2。3, 径向颜色渐变(QRadialGradient
Class)
- 径向渐变有两种:简单径向渐变(
simple radial gradient
)和扩展径向渐变(extended radial gradient
)。其实也没有太大的不同(从它们使用同一个类QRadialGradient
来构造就可以看的出),只是它们的确不一样,所以需要不同的名字来区分。
- 没错,提到径向,这种渐变的确是用圆来确定填充方向。
- 在开始径向渐变前,我们先停一停,回顾前面两种渐变,我们为了确定填充方向其实引入了几个概念,线性渐变中的起点(
start
)和结束点(finalStop
);锥向渐变中的中心点(center
)和角度(angle
);这些概念是把握不同渐变区别和用法的一个关键点,所以径向渐变中有圆心(center
)、半径(centerRadius
)、焦点(focalPoint
)和焦半径(focalRadius
)。来,让我们将这些烙在心头:
2。3。1, 简单径向渐变
QRadialGradient(qreal cx, qreal cy, qreal radius)
QRadialGradient(const QPointF ¢er, qreal radius)
QRadialGradient(qreal cx, qreal cy, qreal radius, qreal fx, qreal fy)
QRadialGradient(const QPointF ¢er, qreal radius, const QPointF &focalPoint)
- 圆心(
center
)和半径(radius
)确定出一个圆周,所以简单径向渐变是在焦点与所有圆周上的点之间进行颜色插值。
- 先看一个指定焦点的例子:
QRadialGradient radialGradient(0, 0, 100, 30, -40);
^^^^^^^
radialGradient.setColorAt(0, Qt::white);
radialGradient.setColorAt(1, Qt::black);
painter->setBrush(QBrush(radialGradient));
QRectF rect(-100, -100, 200, 200);
painter->drawEllipse(rect);
painter->setPen(QPen(Qt::red));
painter->drawLine(-20, 0, 20, 0);
painter->drawLine(0, 20, 0, -20);
painter->setPen(QPen(Qt::blue));
painter->drawLine(30, -30, 30, -50);
painter->drawLine(20, -40, 40, -40);
painter->setPen(QPen(Qt::black));
QRadialGradient radialGradient(0, 0, 100);
radialGradient.setColorAt(0, Qt::white);
radialGradient.setColorAt(1, Qt::black);
painter->setBrush(QBrush(radialGradient));
QRectF rect(-100, -100, 200, 200);
painter->drawEllipse(rect);
painter->setPen(QPen(Qt::red));
painter->drawLine(-20, 0, 20, 0);
painter->drawLine(0, 20, 0, -20);
painter->setPen(QPen(Qt::blue));
painter->drawLine(-20, 0, 20, 0);
painter->drawLine(0, 20, 0, -20);
- 上面两种都属于简单径向渐变,其中后一种又可以看成前一种的特殊情况。
- 简单径向渐变中这种确定填充方向的方式(焦点和中心圆),很自然的让我们考虑到点和圆的位置关系:上面都属于点在圆内的情况;点在圆上时也没有什么不同;但当点在圆外时,简单径向渐变会将其对齐到圆上,这一点与扩展径向渐变不同。
2。3。2, 扩展径向渐变
- 扩展径向渐变与简单径向渐变有两点不同:
- 一是扩展径向渐变中不再用焦点,取而代之的是一个焦圈,所以在构造扩展径向渐变时我们需要多提供一个焦半径(
focusRadius
)。
- 二是在简单径向渐变中焦点在圆外时会被强制对齐到圆上,扩展径向渐变没有这个限制。
- 下面是扩展径向渐变的构造方法:
QRadialGradient(qreal cx, qreal cy, qreal centerRadius, qreal fx, qreal fy, qreal focalRadius)
QRadialGradient(const QPointF ¢er, qreal centerRadius, const QPointF &focalPoint, qreal focalRadius)
QRadialGradient radialGradient(0, 0, 200, 60, 80, 50);
^^
radialGradient.setColorAt(0, Qt::white);
radialGradient.setColorAt(1, Qt::black);
QBrush brush(radialGradient);
painter->setBrush(brush);
painter->drawEllipse(-200, -200, 400, 400);
painter->setPen(QPen(Qt::red));
painter->drawLine(-30, 0, 30, 0);
painter->drawLine(0, 30, 0, -30);
painter->setPen(QPen(Qt::blue));
painter->drawLine(60, 60, 60, 100);
painter->drawLine(40, 80, 80, 80);
- 很明显,这次不再是一个焦点,而是一个焦圈。
- 和简单径向渐变一样,扩展径向渐变也存在一对位置关系:中心圆和焦点圆之间。
- 很奇怪,只有内含时(也就是上例的情况)才可以达到预期效果。在评论区留下你对其它位置关系的理解。。。
- 同样,我们提供两个颜色的同时,也提供了一个位置参数(
position
),它的含义本质上和前两种渐变一样,只不过这里确定的是不同大小的同心圆。
- 以默认焦点的简单径向渐变举个例子:
3, 渐变传播(Spread
)
- 好吧,之前我们一直有意掩饰一点,来看看前面三种渐变的构造方式:
QLinearGradient linearGradient(QPointF(0, -200), QPointF(0, 200));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
linearGradient.setColorAt(0, Qt::white);
linearGradient.setColorAt(1, Qt::black);
...
painter->drawRect(-100, -200, 200, 400);
^^^^^^^^^^^^^^^^^^^^
QConicalGradient conicalGradient(QPointF(0, 0), 45);
conicalGradient.setColorAt(0, Qt::white);
conicalGradient.setColorAt(1, Qt::black);
...
painter->drawRect(-100, -100, 200, 200);
QRadialGradient radialGradient(0, 0, 100);
^^^^^^^^^
radialGradient.setColorAt(0, Qt::white);
radialGradient.setColorAt(1, Qt::black);
...
painter->drawEllipse(-100, -100, 200, 200);
^^^^^^^^^^^^^^^^^^^^
- 把握一点:在创建一个渐变时,我们提供的参数(如
start
和finalStop
)在确定了填充方向的同时,也定义出一个渐变的边界。
- 不难发现,线性渐变中我们定义的起点到结束点的线段刚好能全部覆盖要填充矩形的宽度,换句话说,我们定义的颜色渐变边界和我们将要填充的区域恰好一样大。
- 径向渐变也是如此,我们故意让径向渐变的中心圆和要填充的圆一样大。
- 很自然的一个问题:如果我们构造的颜色渐变边界比要填充的图形小,这时边界之外,图形之间会发生什么?
- 这之间发生的事便是渐变的传播(
spread
),具体怎样传播,需要我们设置传播方式(spread method
)。
- 这里我们并没有提锥向渐变,因为传播涉及的是渐变边界之外发生的事,而从锥向渐变的构造也可以看出,锥向渐变填充的是全平面,它并没有边界之外这种概念,所以传播的设置对锥向渐变没有影响。
- 总结一下:
- Qt默认使用的传播方式是Pad。
- Qt中使用
setSpread(QGradient::Spread method)
设置使用哪一种传播方式。
- 如果想使用Repeat方式可以写类似下面的代码:
QRadialGradient radialGradient = ...
radialGradient.setColorAt(0, Qt::white);
radialGradient.setColorAt(1, Qt::black);
radialGradient.setSpread(QGradient::RepeatSpread);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
painter->setBrush(QBrush(radialGradient));
...
- 下面看看这几种传播方式的样子和区别。
- 为了显得这篇文章没那么冗长,这部分不再贴代码了。与前面的示例代码相比,这部分的代码只是多了上面那句传播方式的设置和一些位置调整。
- 当我们将渐变边界缩至中间那一窄条时,传播方式发挥了作用。
- 在渐变边界以外(待填充区域之内),
PadSpread
方式延续了起点与结束点各自的颜色;
RepeatSpread
方式,如其名字所暗示的那样,不断重复渐变边界内的这一颜色渐变模式。
- 而
ReflectSpread
方式将渐变边界内的模式不断往外镜像(反射)。
- 再来看看径向渐变(焦点与圆心重合)中的传播:
- 这和线性渐变一样!只是表现形式稍有改变:不再是矩形,而是一个个圆环。
- 当焦点不在圆心处时,唯一不同在于颜色的变化没有那么均匀:
- 显而易见,对于
RepeatSpread
,渐变边界之外的填充区域(如红箭头和绿箭头)一直在重复蓝箭头和黄箭头,对于ReflectSpread
,渐变边界之外的填充区域(如红箭头和绿箭头)一直在镜像(反射)蓝箭头和黄箭头。
- 这就是渐变传播的全部。
4, 渐变颜色
- 我们之前的例子都难免有些单调。因为只使用了渐变边界的两个端点(
position
是0或1),而且只使用了黑色和白色。
- 世界本应该是五彩缤纷的:)
- 再看看我们提供渐变颜色的代码:
linearGradient.setColorAt(0, Qt::white);
linearGradient.setColorAt(1, Qt::black);
- 所以我们可以在任意多的比例(
position
)处插入任何颜色:
linearGradient.setColorAt(0, QColor("#020024"));
linearGradient.setColorAt(0.35, QColor("#090979"));
linearGradient.setColorAt(1, QColor("#00d4ff"));
- 结果是一个漂亮的颜色过渡:
- 一个漂亮的颜色过渡并不是随意颜色的拼凑,颜色过渡也有一些预制的主题,感谢那些卓越的设计师和开源组织~
- 推荐两个很棒的网站:
https://cssgradient.io/
https://webgradients.com/
- 基于后一个网站,Qt提供了内置的渐变主题(
Preset
),这些主题以枚举(enum QGradient::Preset
)的方式提供,注意这个枚举是 Qt 5.12引入的。
- 下面是绘制前几个内置渐变主题的小例子:
const qreal radius = 60;
const qreal gap = 30;
const qreal distance = 2*radius+gap;
QRectF circle(0, -1*radius, 2*radius, 2*radius);
for (int i = 1; i <= 7; ++i) {
painter->setBrush(QBrush(QGradient(static_cast<QGradient::Preset>(i))));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
painter->drawEllipse(circle);
qreal x = (i%5) ? distance : -4*distance;
qreal y = (i%5) ? 0 : distance;
circle.translate(x, y);
}
- 绘制结果如下:
- 渐变的实际用例并不难找:
5, Qt例子
- Qt官方提供了一个不错的例子
Gradients
:
https://doc.qt.io/qt-5/qtwidgets-painting-gradients-example.html
6, 参考
- https://doc.qt.io/qt-5/qgradient.html
- https://developer.mozilla.org/en-US/docs/Web/CSS/gradient
- https://xd.adobe.com/ideas/principles/web-design/gradient-color-definition/
- 本文的文字和图片可以随意使用,不用找我允许。能放个出处当然更好啦~
- 推荐下个人公众号,会写些好玩的,也可以一块学习~
- 肝完了。。。