使用 Qt QGraphicsView/QGraphicsScene 绘制色轮

使用 Qt QGraphicsView/QGraphicsScene 绘制色轮

本文介绍如何在 Qt 中利用 QGraphicsViewQGraphicsScene 实现基础圆形绘制,以及进阶的色轮(Color Wheel)效果。

色轮是色彩选择器的常见控件,广泛应用于图形设计、绘画和 UI 取色等场景。本文将详细讲解两种常见的色轮实现方式,并配以完整代码和效果图。


一、QGraphicsView/QGraphicsScene 绘制圆形

1. 原理说明

Qt 的 QGraphicsView/QGraphicsScene 提供了强大的 2D 图形视图框架。QGraphicsScene 负责管理所有图形项,QGraphicsView 负责显示场景内容。

绘制圆形时,常用 QGraphicsEllipseItem,通过设置其矩形区域和填充颜色即可实现。

2. 代码实现

假设我们已经创建了一个 Qt Widgets Application 项目 Scence1。在类的构造函数中创建 QGraphicsSceneQGraphicsView,并添加到 QVBoxLayout 布局中:

cpp 复制代码
Scence1::Scence1(QWidget *parent)
        : QWidget(parent)
{
    // 创建场景
    QGraphicsScene* scene = new QGraphicsScene(this);

    // 创建视图并设置场景
    QGraphicsView* view = new QGraphicsView(scene, this);

    paintEllipse(view, scene); // 绘制椭圆

    //paintColorWheel(view, scene); // 绘制色轮

    // 使用布局管理器添加视图到窗口
    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->addWidget(view);
    setLayout(layout);

    ui.setupUi(this);
}

void Scence1::paintEllipse(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置画笔为蓝色,宽度为1
    QPen pen;
    pen.setColor(QColor(0, 0, 255));
    pen.setWidth(1);
    // 圆的半径
    int circleR = 160;
    // 创建一个椭圆
    QGraphicsEllipseItem* myEllipseItem = new QGraphicsEllipseItem();
    myEllipseItem->setRect(0, 0, 2 * circleR, 2 * circleR);
    myEllipseItem->setPen(pen);
    myEllipseItem->setBrush(QColor(0, 0, 255));
    // 添加到场景
    scene->addItem(myEllipseItem);
    // 设置视图的场景
    view->setScene(scene);
}

这样我们可以得到一个蓝色的圆形,完成了第一步:

二、QGraphicsView/QGraphicsScene 实现色轮

色轮是色彩学中常用的工具,通常以 HSV 色彩空间为基础。HSV 色彩空间将颜色分为色相(Hue)、饱和度(Saturation)、明度(Value),非常适合用于色轮的实现。

原理说明

色轮的本质是将色相(Hue)映射到圆周上,不同的实现方式可以带来不同的视觉效果和性能表现。

常见的两种实现方式如下:

方式一:多个扇形拼接色轮

实现思路

将圆分成若干个扇形,每个扇形代表一种色相(Hue)。

通过 HSV 颜色模型,改变色相值,生成不同颜色。

使用 QPainterPath 绘制扇形,并填充对应颜色。

每个扇形作为一个 QGraphicsPathItem 添加到 scene,便于后续交互。

代码实现

cpp 复制代码
void Scence1::paintColorWheel(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置圆的半径
    int radius = 150;
    // 设置扇形数量
    int numSegments = 360;
    // 遍历每个扇形
    for (int i = 0; i < numSegments; ++i) {
        // 计算当前扇形的起始角度和结束角度
        qreal startAngle = i * 16;
        qreal endAngle = (i + 1) * 16;
        // 创建一个 QPainterPath 对象
        QPainterPath path;
        // 移动到圆心
        path.moveTo(0, 0);
        // 绘制扇形路径
        path.arcTo(-radius, -radius, 2 * radius, 2 * radius, startAngle, 16);
        // 填充颜色
        QColor color = QColor::fromHsv(i, 255, 255);
        QBrush brush(color);
        // 创建一个 QGraphicsPathItem 对象
        QGraphicsPathItem* item = new QGraphicsPathItem(path);
        item->setBrush(brush);
        // 添加到场景
        scene->addItem(item);
    }
    // 设置视图的场景
    view->setScene(scene);
}

这样我们就实现了一个简单的色轮效果:

方式二:使用渐变色填充色轮

实现思路

通过 QConicalGradient 创建圆锥形渐变,渐变的颜色从中心向外辐射。

使用 QGraphicsEllipseItem 绘制一个完整的圆形作为色轮的底图。

代码实现

cpp 复制代码
void Scence1::paintColorWheel(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置圆的半径
    int radius = 150;
    // 创建一个 QConicalGradient 渐变对象
    QConicalGradient gradient(0, 0, 0);
    // 添加颜色停靠点
    for (int i = 0; i < 360; i += 10) {
        gradient.setColorAt(i / 360.0, QColor::fromHsv(i, 255, 255));
    }
    // 创建一个 QGraphicsEllipseItem 对象
    QGraphicsEllipseItem* item = new QGraphicsEllipseItem();
    item->setRect(-radius, -radius, 2 * radius, 2 * radius);
    // 设置渐变填充
    item->setBrush(gradient);
    // 添加到场景
    scene->addItem(item);
    // 设置视图的场景
    view->setScene(scene);
}

这种方式实现的色轮更加平滑,过渡自然:

三、总结

本文介绍了如何使用 Qt 的 QGraphicsViewQGraphicsScene 实现圆形及色轮的绘制。

通过两种方式实现色轮:一种是通过多个扇形拼接而成,另一种是使用渐变色填充。读者可以根据需求选择合适的实现方式。


附录:完整代码

cpp 复制代码
#include "scence1.h"
#include "ui_scence1.h"

Scence1::Scence1(QWidget *parent)
        : QWidget(parent)
{
    // 创建场景
    QGraphicsScene* scene = new QGraphicsScene(this);

    // 创建视图并设置场景
    QGraphicsView* view = new QGraphicsView(scene, this);

    paintEllipse(view, scene); // 绘制椭圆

    //paintColorWheel(view, scene); // 绘制色轮

    // 使用布局管理器添加视图到窗口
    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->addWidget(view);
    setLayout(layout);

    ui.setupUi(this);
}

void Scence1::paintEllipse(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置画笔为蓝色,宽度为1
    QPen pen;
    pen.setColor(QColor(0, 0, 255));
    pen.setWidth(1);
    // 圆的半径
    int circleR = 160;
    // 创建一个椭圆
    QGraphicsEllipseItem* myEllipseItem = new QGraphicsEllipseItem();
    myEllipseItem->setRect(0, 0, 2 * circleR, 2 * circleR);
    myEllipseItem->setPen(pen);
    myEllipseItem->setBrush(QColor(0, 0, 255));
    // 添加到场景
    scene->addItem(myEllipseItem);
    // 设置视图的场景
    view->setScene(scene);
}

void Scence1::paintColorWheel(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置圆的半径
    int radius = 150;
    // 设置扇形数量
    int numSegments = 360;
    // 遍历每个扇形
    for (int i = 0; i < numSegments; ++i) {
        // 计算当前扇形的起始角度和结束角度
        qreal startAngle = i * 16;
        qreal endAngle = (i + 1) * 16;
        // 创建一个 QPainterPath 对象
        QPainterPath path;
        // 移动到圆心
        path.moveTo(0, 0);
        // 绘制扇形路径
        path.arcTo(-radius, -radius, 2 * radius, 2 * radius, startAngle, 16);
        // 填充颜色
        QColor color = QColor::fromHsv(i, 255, 255);
        QBrush brush(color);
        // 创建一个 QGraphicsPathItem 对象
        QGraphicsPathItem* item = new QGraphicsPathItem(path);
        item->setBrush(brush);
        // 添加到场景
        scene->addItem(item);
    }
    // 设置视图的场景
    view->setScene(scene);
}

void Scence1::paintColorWheel(QGraphicsView* view, QGraphicsScene* scene)
{
    // 设置圆的半径
    int radius = 150;
    // 创建一个 QConicalGradient 渐变对象
    QConicalGradient gradient(0, 0, 0);
    // 添加颜色停靠点
    for (int i = 0; i < 360; i += 10) {
        gradient.setColorAt(i / 360.0, QColor::fromHsv(i, 255, 255));
    }
    // 创建一个 QGraphicsEllipseItem 对象
    QGraphicsEllipseItem* item = new QGraphicsEllipseItem();
    item->setRect(-radius, -radius, 2 * radius, 2 * radius);
    // 设置渐变填充
    item->setBrush(gradient);
    // 添加到场景
    scene->addItem(item);
    // 设置视图的场景
    view->setScene(scene);
}



void Scence1::paintAxis(QGraphicsScene* scene, int hueCircleR)
{
    // 画三条分割线
    QPen pen;
    pen.setWidth(1);
    pen.setColor(QColor(0, 0, 0));
    pen.setStyle(Qt::DashDotLine);
    pen.setWidth(1);

    // 分割线1:210度到30度
    QGraphicsLineItem* splitLine1 = new QGraphicsLineItem();
    splitLine1->setLine(QLineF(
        hueCircleR + hueCircleR * cos(210 * 3.14159 / 180), hueCircleR - hueCircleR * sin(210 * 3.14159 / 180),
        hueCircleR + hueCircleR * cos(30 * 3.14159 / 180), hueCircleR - hueCircleR * sin(30 * 3.14159 / 180)));
    splitLine1->setPen(pen);

    // 分割线2:270度到90度
    QGraphicsLineItem* splitLine2 = new QGraphicsLineItem();
    splitLine2->setLine(QLineF(
        hueCircleR + hueCircleR * cos(270 * 3.14159 / 180), hueCircleR - hueCircleR * sin(270 * 3.14159 / 180),
        hueCircleR + hueCircleR * cos(90 * 3.14159 / 180), hueCircleR - hueCircleR * sin(90 * 3.14159 / 180)));
    splitLine2->setPen(pen);

    // 分割线3:330度到150度
    QGraphicsLineItem* splitLine3 = new QGraphicsLineItem();
    splitLine3->setLine(QLineF(
        hueCircleR + hueCircleR * cos(330 * 3.14159 / 180), hueCircleR - hueCircleR * sin(330 * 3.14159 / 180),
        hueCircleR + hueCircleR * cos(150 * 3.14159 / 180), hueCircleR - hueCircleR * sin(150 * 3.14159 / 180)));
    splitLine3->setPen(pen);

    // 添加分割线到场景
    scene->addItem(splitLine1);
    scene->addItem(splitLine2);
    scene->addItem(splitLine3);

    // 设置字体
    QFont font("Arial", 8);
    font.setBold(true);

    // 添加文字标注
    QGraphicsTextItem* textItem1 = new QGraphicsTextItem();
    textItem1->setPlainText(QString("CB\n210"));
    textItem1->setFont(font);
    textItem1->setPos(hueCircleR + hueCircleR * cos(210 * 3.14159 / 180) - 30, hueCircleR - hueCircleR * sin(210 * 3.14159 / 180) - 20);

    QGraphicsTextItem* textItem2 = new QGraphicsTextItem();
    textItem2->setPlainText(QString("30\nRY"));
    textItem2->setFont(font);
    textItem2->setPos(hueCircleR + hueCircleR * cos(30 * 3.14159 / 180) + 5, hueCircleR - hueCircleR * sin(30 * 3.14159 / 180) - 20);

    QGraphicsTextItem* textItem3 = new QGraphicsTextItem();
    textItem3->setPlainText(QString("BM 270"));
    textItem3->setFont(font);
    textItem3->setPos(hueCircleR + hueCircleR * cos(270 * 3.14159 / 180) - 30, hueCircleR - hueCircleR * sin(270 * 3.14159 / 180) + 0);

    QGraphicsTextItem* textItem4 = new QGraphicsTextItem();
    textItem4->setPlainText(QString("90 YG"));
    textItem4->setFont(font);
    textItem4->setPos(hueCircleR + hueCircleR * cos(90 * 3.14159 / 180) - 20, hueCircleR - hueCircleR * sin(90 * 3.14159 / 180) - 20);

    QGraphicsTextItem* textItem5 = new QGraphicsTextItem();
    textItem5->setPlainText(QString("330\nMR"));
    textItem5->setFont(font);
    textItem5->setPos(hueCircleR + hueCircleR * cos(330 * 3.14159 / 180) + 5, hueCircleR - hueCircleR * sin(330 * 3.14159 / 180) - 20);

    QGraphicsTextItem* textItem6 = new QGraphicsTextItem();
    textItem6->setPlainText(QString("GC\n150"));
    textItem6->setFont(font);
    textItem6->setPos(hueCircleR + hueCircleR * cos(150 * 3.14159 / 180) - 30, hueCircleR - hueCircleR * sin(150 * 3.14159 / 180) - 20);

    // 添加文字到场景
    scene->addItem(textItem1);
    scene->addItem(textItem2);
    scene->addItem(textItem3);
    scene->addItem(textItem4);
    scene->addItem(textItem5);
    scene->addItem(textItem6);
}

Pos(hueCircleR + hueCircleR * cos(150 * 3.14159 / 180) - 30, hueCircleR - hueCircleR * sin(150 * 3.14159 / 180) - 20);

    // 添加文字到场景
    scene->addItem(textItem1);
    scene->addItem(textItem2);
    scene->addItem(textItem3);
    scene->addItem(textItem4);
    scene->addItem(textItem5);
    scene->addItem(textItem6);
}
相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜9 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa9 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio