软件是对现实世界的抽象,在设计软件接口时通常也会参考现实世界来进行设计。
在现实世界中,画家使用画笔、画刷这些工具在宣纸上作画,而在 Qt 中,画家是 QPainter 对象(画家的大脑则是 QPaintEngine),纸张可以是 QPaintDevice 的任何子类对象,如 QWidget、QImage、QPixmap、QGLPixelBuffer 等;在 QPainter 选好纸张后,就可以使用画笔来勾勒物体的轮廓了,Qt 中的画笔是 QPen 对象;画完轮廓后,可能还需要使用画刷来填充轮廓,Qt 中的画刷是 QBrush 对象。
一、QPaintDevice
本节介绍 QPaintDevice 的几个常用方法。
devicePixelRatio() 与 devicePixelRatioF()
用于获取DPI的缩放倍数,需要先在程序中开启DPI缩放特性:
1 | int main(int argc, char *argv[]) |
从 Qt 5.14 版本开始,Qt可以支持小数位的 DPI 缩放,如 1.25、1.75 等。如需获取小数位DPI值,需要先设置Qt::HighDpiScaleFactorRoundingPolicy::PassThrough
策略,然后通过 devicePixelRatioF 方法获取。
width() 与 height()
用于获取 QPaintDevice 的宽和高,这个数值不会随着坐标系的改变而改变,而且也不会随着 DPI 的改变而改变。
widthMM() 与 heightMM()
用于获取 QPaintDevice 在屏幕上实际显示的宽和高,单位为毫米。
二、QPen
画笔有宽度(width/widthF)、颜料(brush)、线条样式(Style)、一条线起笔和落笔的样式(CapStyle)、两条线相交处的样式(JoinStyle)等属性。
2.1 宽度
1 | void QPen::setWidth(int width) |
如果指定的宽度小于 0,将不会绘制任何内容;如果指定的宽度等于 0,将被视为 1 个像素宽度;如果指定的宽度大于等于 1,则按指定的宽度来绘制。
2.2 QPen的画刷
1 | void QPen::setBrush(const QBrush &brush) |
QPen 有画刷属性,QPainter 对象也画刷属性(下面章节会介绍),二者都有填充之意,QPen 的画刷是用来填充画笔所画的线条(可以理解为画笔的颜料)的,而QPainter 的画刷是用来填充画笔所画的轮廓区域的,如填充矩形里面的区域。
画刷可以是纯色、渐变色,也可以是图片。
2.3 线条样式
1 | void QPen::setStyle(Qt::PenStyle style) |
2.4 起落笔样式
1 | void QPen::setCapStyle(Qt::PenCapStyle style) |
2.5 拐点样式
1 | void QPen::setJoinStyle(Qt::PenJoinStyle style) |
2.6 实例
下面示例指定了画笔的如下属性:
- 画笔的宽度为 70px
- 使用红色画刷
- 线条样式为通过若干空格分割的点线状
- 线条起笔和落笔处为圆形
- 线条拐点处也为圆形
1 | void Qt2DSample::paintEvent(QPaintEvent* e) { |
实际绘制出来的效果如下图所示:
上面画笔使用红色作为颜料,也可以使用渐变色作为颜料,当然还可以使用图片作为颜料。
下面我们将颜料更改为一个红色的心形♥图片:
1 | void Qt2DSample::paintEvent(QPaintEvent* e) { |
绘制的效果如下:
三、QBrush
QPen 与 QPainter 都有画刷属性,这里的 QBrush 是指的 QPainter 的画刷。虽然与 QPen 的画刷都为 QBrush 类型,但二者是有区别的,QPen 的画刷是用来填充画笔所画线条(可以理解为画笔的颜料)的,而 QPainter 的画刷是用来填充画笔所画的轮廓所包围的区域,如填充矩形里面的区域。
如下面示例:
1 | void Qt2DSample::paintEvent(QPaintEvent* e) { |
实际绘制的矩形如下图所示:
QBrush 有样式(Style)、颜色(Color)、渐变色(Gradient)、纹理图片(Texture)等属性。
3.1 样式
画刷的作用是填充区域,画刷的样式则是用来指定通过什么模式来填充区域的。
下图列出了 QBrush 支持的样式:
QBrush的初始样式是 Qt::NoBrush,即不做填充操作,该值显然是没有任何意义的,下面是一个错误的使用示例;
1 | // 错误示例 |
我们可以通过构造函数和 setStyle 方法来设置样式:
1 | QBrush(Qt::BrushStyle style) |
当通过不同的方式构造 QBrush 对象时,QBrush 的样式也会自动被设置为不同的值:
1 | // 样式自动被设置为渐变样式:Qt::LinearGradientPattern, Qt::RadialGradientPattern 或 Qt::ConicalGradientPattern |
3.2 纹理图片
上面2.6节的实例中,在设置心形图片作为QPen的画刷时,使用的就是纹理图片。
下面汇总了设置 QBrush 纹理图片的几种方法:
1 | // 通过构造函数 |
通过上面方式设置纹理图片后,QBrush 样式会被自动修改为 Qt::TexturePattern,因为纹理图片只能与 Qt::TexturePattern 样式配合使用,否则纹理图片将失效。
3.3 纯色
可以为画刷指定纯色或渐变色,纯色使用 QColor 对象表示。
常用的构造 QColor 对象的方式有如下几种:
1 | QColor("#FFFFFF"); // r g b |
3.4 渐变色
QBrush 只提供了一种方式来指定渐变色:
1 | QBrush(const QGradient &gradient) |
在Qt中,渐变色使用 QGradient 及其子类表示,目前Qt支持三种类型的渐变:
- 线性渐变,对应 QLinearGradient 类。
- 锥形渐变,对应 QConicalGradient 类。
- 径向渐变,又分为简单径向渐变和扩展径向渐变,二者都对应 QRadialGradient 类。
下图是一个从左到右,由白到黑的渐变过程:
填充方向既然可以从左到右、从右到左的、从里到外,是可以360度旋转等等,不同的填充方向造就了不同类型的渐变,上述三种类型的渐变主要区别也在填充方向的不同。
3.4.1 线性渐变
QLinearGradient 构造函数如下:
1 | QLinearGradient(qreal x1, qreal y1, qreal x2, qreal y2) |
通过指定一个起点和一个终点就可以确定一条线段的长度及方向,这个方向就是填充方向,渐变范围基于该长度而确定(详见后面的“渐变范围”章节)。
线性渐变的方向可以是水平的,也可以是垂直的,还可以是斜着的,具体方向由线段与X轴的夹角来决定。
下面我们定义一个起点为(0,0)、终点为(0,400),垂直方向的线性渐变,然后使用该渐变填充位置在 (0,0),宽高为 200x400 的矩形(请注意矩形的位置和大小,这个与渐变的起止位置有关,后面会详细介绍):
1 | void Qt2DSample::paintEvent(QPaintEvent* e) { |
效果如下图所示:
由于是垂直方面的渐变,所以渐变色从上到下变化的,颜色起始于黑色,终止于白色。
渐变色的起止颜色由setColorAt
方法设置,该方法原型如下:
1 | void QGradient::setColorAt(qreal position, const QColor &color) |
setColorAt 不仅接受颜色参数,还支持 postion 参数,position 参数的范围是[0,1]
。线性渐变通过起止位置确定了一条带方向的线段,而 position 参数则用于确定线段上的一个具体位置,结合position和color参数就可以表示在某一位置的颜色值。
可以使用 setColorAt 方法指定多个在[0,1]
之间的位置及该位置的颜色,Qt会自动在多个颜色之间插入过渡色,达到平滑过渡的效果。
现在我们在 0.3 位置插入一个骚气的红色,0.5位置插入黄色,其他都保持不变:
1 | linearGrad.setColorAt(0, Qt::black); |
效果如下图所示:
3.4.2 渐变范围
在上面一节,我们强调了所绘制的矩形的位置/大小与渐变的起止位置有关,而且又强调了 position 参数所确定是线段上的具体位置。其根本原因就在于渐变是有位置和范围的。
对于线性渐变而言,渐变的范围是起始点和终止点间的线段,但颜色在垂直于该线段的方向却是无限扩充的,下面示例可以很好的说明这一特性。
1 | // Qt2DSample窗口长宽为600x600 |
效果如下图所示:
从上图可以看到,渐变色在垂直于渐变线段(中间矩形的左上角和右下角连成的斜线)方向无限扩展,但为什么在渐变范围边界以外还有颜色呢?如左上角的黄色和右下角的白色,这个与渐变的另一个属性特性有关 – 渐变传播,后面章节有详细介绍。
3.4.3 锥向渐变
QConicalGradient 类的构造函数如下:
1 | QConicalGradient(qreal cx, qreal cy, qreal angle) |
锥向渐变通过中心点(center)和起始角度来(angle)来所连接的直线确定填充的初始方向,然后从该直线开始逆时针进行360度填充,而且每个角度的颜色都是无限向外传播的。
1 | // Qt2DSample窗口长宽为200x200 |
效果如下图所示,渐变色从45度开始渐变,并且每个角度都无限向外传播,填充满了整个矩形:
3.4.5 径向渐变
径向渐变分为简单径向渐变和扩展径向渐变,在介绍二者的不同之前,我们先看看QRadialGradient 类的构造函数可以大致分为以下三类:
1 | // 指定中心点、半径、焦点位置、焦半径 |
中心点(center)和半径(centerRadius)组成了一个圆,记为圆A,径向渐变的范围只限于圆A以内,圆A以外的区域按照指定的渐变传播方式进行传播;
焦点(focalPoint)为平面上的的一个点,简单径向渐变和扩展径向渐变对焦点的位置有不同的要求,这个区别在后面会介绍;
焦半径(focalRadius)则是围绕焦点所作的另外一个圆(记为圆B)的半径。
扩展径向渐变是在简单径向渐变的基础上额外使用了 focalRadius
(焦点半径)这个参数。
简单径向渐变
焦点和圆A圆弧上的每一个点都可以组成一条线段,如果按照1度取一个点,就有360个线段,简单径向渐变就是对这360个线段都进行线性渐变。
对于简单径向渐变而言,允许焦点位于圆弧上和圆外,如果焦点位于圆外,会自动将其退缩到圆弧上。
下面是一个简单径向渐变的例子:
1 | // Qt2DSample窗口长宽为600x600 |
效果如下图所示,径向渐变的范围只限于圆内,圆外的区域默认采用PadSpread方式传播:
扩展径向渐变
扩展径向渐变使用焦点和焦半径又组成了一个圆B,与简单径向渐变类似,也是通过对圆A圆弧上的每个点的连线做线性渐变来实现,但这次不是焦点与圆A圆弧上的点进行连线,而是“圆B圆弧上的点”与“圆A圆弧上的点”的连线,对这些连线做线性渐变。
圆B始终填充起始颜色。
与简单径向渐变不同的是,圆B必须完全在圆A里面。
下面是一个扩展径向渐变的例子:
1 | // Qt2DSample窗口长宽为600x600 |
效果如下图所示:
3.4.6 渐变传播
在Qt中通过 setSpread 方法设置渐变传播方式:
1 | void QGradient::setSpread(QGradient::Spread method) |
QGradient::Spread枚举定义了三种传播方式:
- QGradient::PadSpread
- QGradient::RepeatSpread
- QGradient::ReflectSpread
默认为传播方式为 QGradient::PadSpread,而且由锥向渐变的行为可知,锥向渐变是没有渐变传播一说的,因此它始终都是360度渐变。
下图形象说明了三种传播方式的差异:
- PadSpread 方式延续了起点与终点处各自的颜色,无限传播
- RepeatSpread 方式不断重复渐变边界内的这一颜色的渐变模式
- ReflectSpread 方式将渐变边界内的模式不断往外镜像反射
3.4.7 渐变主题
设计一个漂亮的渐变色并非易事,Qt提供了内置的渐变主题(Preset),这些主题通过 QGradient::Preset 枚举类型提供(该枚举从 Qt 5.12 开始引入)。
从Qt官方文档得知,QGradient::Preset预置渐变主题方案是基于https://webgradients.com/实现的。
QGradient 类支持通过QGradient::Preset 枚举类型直接构造:
1 | QGradient::QGradient(QGradient::Preset preset) |
下面是使用QGradient::Preset的示例:
1 | void Qt2DSample::paintEvent(QPaintEvent* e) { |
3.4.8 网站推荐
下面几个网站也提供了漂亮的渐变色方案,可以用作参考:
限于政策原因,在您看到该文章时,博客可能已经关闭了评论功能🥺
您可以通过在 blog-comment 项目中提交Issue来间接地发表评论🍀