首页 Qt 学习之路 2 Qt 学习之路 2(27):渐变

Qt 学习之路 2(27):渐变

17 2.7K

渐变是绘图中很常见的一种功能,简单来说就是可以把几种颜色混合在一起,让它们能够自然地过渡,而不是一下子变成另一种颜色。渐变的算法比较复杂,写得不好的话效率会很低,好在很多绘图系统都内置了渐变的功能,Qt 也不例外。渐变一般是用在填充里面的,所以,设置渐变是在QBrush里面。

Qt 提供了三种渐变:线性渐变(QLinearGradient)、辐射渐变(QRadialGradient)和角度渐变(QConicalGradient)。我们可以在 Qt API 手册中看到这几种渐变的区别:

线性渐变:
QLinearGradient 示例

辐射渐变:
QRadialGradient 示例

角度渐变:
QConicalGradient 示例

具体细节可以参考文档。下面我们通过一个示例看看如何使用渐变进行填充:

void paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    painter.setRenderHint(QPainter::Antialiasing, true);
    QLinearGradient linearGradient(60, 50, 200, 200);
    linearGradient.setColorAt(0.2, Qt::white);
    linearGradient.setColorAt(0.6, Qt::green);
    linearGradient.setColorAt(1.0, Qt::black);
    painter.setBrush(QBrush(linearGradient));
    painter.drawEllipse(50, 50, 200, 150);
}

像以前一样,我们也只给出了paintEvent()的代码。这段代码看起来也相当清晰:首先我们打开了反走样,然后创建一个QLinearGradient对象实例。QLinearGradient也就是线性渐变,其构造函数有四个参数,分别是 x1,y1,x2,y2,即渐变的起始点和终止点。在这里,我们从 (60, 50) 点开始渐变,到 (200, 200) 点止。关于坐标的具体细节,我们会在后面的章节中详细介绍。渐变的颜色是在setColorAt()函数中指定的。下面是这个函数的签名:

void QGradient::setColorAt ( qreal position, const QColor & color )

这个函数的作用是,把 position 位置的颜色设置成 color。其中,position 是一个 [0, 1] 闭区间的数字。也就是说,position 是相对于我们建立渐变对象时做的那个起始点和终止点区间的一个比例。以这个线性渐变为例,在从 (60, 50) 到 (200, 200) 的线段上,在 0.2,也就五分之一处设置成白色,在 0.6 也就是五分之三处设置成绿色,在 1.0 也就是终点处设置成黑色。创建QBrush对象时,把这个渐变对象传递进去,然后就可以运行了:

线性渐变示例

下面我们开始一个更复杂,也更实用一些的例子:绘制一个色轮(color wheel)。所谓色轮,其实就是一个带有颜色的圆盘(或许你没听说过这个名字,但是你肯定见过这个东西),下面是色轮的运行结果:

色轮示例

我们来看看它的代码:

void ColorWheel::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    const int r = 150;
    QConicalGradient conicalGradient(0, 0, 0);

    conicalGradient.setColorAt(0.0, Qt::red);
    conicalGradient.setColorAt(60.0/360.0, Qt::yellow);
    conicalGradient.setColorAt(120.0/360.0, Qt::green);
    conicalGradient.setColorAt(180.0/360.0, Qt::cyan);
    conicalGradient.setColorAt(240.0/360.0, Qt::blue);
    conicalGradient.setColorAt(300.0/360.0, Qt::magenta);
    conicalGradient.setColorAt(1.0, Qt::red);

    painter.translate(r, r);

    QBrush brush(conicalGradient);
    painter.setPen(Qt::NoPen);
    painter.setBrush(brush);
    painter.drawEllipse(QPoint(0, 0), r, r);
}

首先还是新建 QPainter 对象,开启反走样。然后我们将圆盘半径定义为 150。下面创建一个角度渐变实例,其构造函数同样接受三个参数:

QConicalGradient::QConicalGradient ( qreal cx, qreal cy, qreal angle )

前两个参数 cx 和 cy 组成角度渐变的中心点,第三个参数是渐变的起始角度。在我们的例子中,我们将渐变中心点设置为 (0, 0),起始角度为 0。类似线性渐变,角度渐变的setColorAt()函数同样接受两个参数,第一个是角度比例,第二个是颜色。例如,

conicalGradient.setColorAt(0.0, Qt::red);

将 0 度角设置为红色;

conicalGradient.setColorAt(60.0/360.0, Qt::yellow);

将 60 度角设置为黄色。由于一个圆周是 360 度,所以 60.0/360.0 即是这个角度的比例。其余代码以此类推。最后一句,我们将 1.0 处设置为红色,也就是重新回到起始处。至于颜色的分布,这是由颜色空间定义的,有兴趣的朋友可以查阅有关颜色模型的理论。

painter.translate(r, r);

这是我们唯一不熟悉的函数。QPainter::translate(x, y)函数意思是,将坐标系的原点设置到 (x, y) 点。原本坐标系原点位于左上角,我们使用translate(r, r),将坐标原点设置为 (r, r)。这么一来,左上角的点的坐标就应该是 (-r, -r)。

最后,我们使用drawEllipse()函数绘制圆盘。注意,由于我们已经把坐标原点设置为 (r, r),因此,在绘制时,圆心应该是新的坐标 (0, 0),而不是原来的 (r, r)。

PS:为了理解translate()函数的作用,可以思考下,如果去掉translate()函数的调用,我们的程序应该如何修改。答案是:

void ColorWheel::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    const int r = 150;
    QConicalGradient conicalGradient(r, r, 0);

    conicalGradient.setColorAt(0.0, Qt::red);
    conicalGradient.setColorAt(60.0/360.0, Qt::yellow);
    conicalGradient.setColorAt(120.0/360.0, Qt::green);
    conicalGradient.setColorAt(180.0/360.0, Qt::cyan);
    conicalGradient.setColorAt(240.0/360.0, Qt::blue);
    conicalGradient.setColorAt(300.0/360.0, Qt::magenta);
    conicalGradient.setColorAt(1.0, Qt::red);

    QBrush brush(conicalGradient);
    painter.setPen(Qt::NoPen);
    painter.setBrush(brush);
    painter.drawEllipse(QPoint(r, r), r, r);
}

不仅我们需要修改最后的绘制语句,还需要注意修改QConicalGradient定义时传入的中心点的坐标。

17 评论

eatinchina 2013年1月10日 - 13:36

画笔渐变呢

回复
豆子 2013年1月10日 - 14:49

QPen 可以使用 QBrush 初始化,可以参考下文档。

回复
qingxp9 2013年4月27日 - 17:20

QConicalGradient::QConicalGradient ( qreal cx, qreal cy, qreal angle ) 这个由cx,cy组成的中心点,设置为0,0 为什么实际是在图形中间,而不是左上角(0,0)点呢? 这个cxcy的具体含义不是很明白

回复
豆子 2013年4月28日 - 09:14

坐标 (cx, cy) 指定的是渐变中心点,它的 (0, 0) 针对的是绘制渐变的图形坐标系,而左上角的 (0, 0) 针对的是窗口坐标系,二者是不一致的。简单地说,在哪里进行渐变,(cx, cy) 就是其所在区域中心。

回复
sxy 2015年5月22日 - 09:49

因为translate了,左上角坐标变成了-150 -150
渐变中心和图像中心都是0 0

回复
qingxp9 2013年4月27日 - 17:35

QPoint(r, r) 这个QPoint(x,y)的作用是什么啊,我以为也是坐标,但将QPoint() 去掉,用x,y直接代替得到的是不一样的填充

回复
qingxp9 2013年4月27日 - 17:48

实验了下drawEllipse(QPoint(r, r), r, r),QPoint表示圆心,第二个参数是横半轴,第三个是竖半轴。 一个新的问题:我将圆心放到左上角,这样就只能显示1/4之一圆 ,但这个1/4圆的填充却并不是显示整圆时候的右下角1/4圆的填充图案,这是为什么呢

回复
豆子 2013年4月28日 - 09:22

不清楚你说的“将圆心放到左上角”什么意思?你的代码是什么?drawEllipse() 的参数换成 x, y 后,(x, y) 指定的是一个椭圆外接矩形的左上角,而使用 QPoint 指定的是绘制椭圆的中心,这一点在文档上有很清晰的描述。如果你不明白,应该先从文档着手。

回复
sxy 2015年5月22日 - 09:45

你需要将渐变中心也调整到左上角就能达到你说的效果了

回复
孙江涛 2015年11月13日 - 11:01

把translate函数放在半径定义的下面可能更好一些

回复
丁蔚 2016年2月15日 - 20:28

请问豆子老师为什么那些60.0 120.0 360.0 等数字不加 .0 的话如 60 则只会出现red的渐变?

回复
木纹秋水 2016年5月15日 - 16:03

请教豆子:QPainter painter(this); 这一段代码为什么要放在void ColorWheel::paintEvent(QPaintEvent *)里,放在构造函数里为什么不行

回复
豆子 2016年5月20日 - 15:08

如果你直接写QPainter painter(this);,对象是建立在栈上的,函数结束之后就会被析构

回复
普法居士 2016年5月31日 - 17:46

有一点不太明白
去掉translate那一行,并把最后的改为
painter.drawEllipse(0, 0, r, r);

为什么只有把渐变中心点改为
QConicalGradient conicalGradient(75, 75, 0);
这样才行?到底是怎么计算的?

回复
豆子 2016年6月3日 - 12:53

在这个例子中,渐变的中心点就是圆心。当有 translate() 函数时,坐标原点发生了变化,注意,QPainter 是一个状态机,虽然 QConicalGradient 的声明在 translate() 函数之前,但是只有在实际绘制时才有效果!因此,使用了 translate() 之后,圆心是 (0, 0),即便 QConicalGradient 的声明在前,但其坐标也必须是 (0, 0);去掉了 translate() 之后,圆心是 (75, 75),QConicalGradient 也需要修改到圆心的位置。

回复
Alien You 2017年5月7日 - 11:24

豆哥左上角的坐标点为什么是(-r,-r)而不是(-r,r)?

回复
豆子 2017年5月8日 - 23:07

translate()函数移动的是原点位置,并不改变坐标轴的方向。数学上 Y 轴是垂直向上,但是 Qt 中的规定 Y 轴正方向是垂直向下。所以移动之后是 (-r, -r),不是 (-r, r)

回复

回复 豆子 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2