使用 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);
}
相关推荐
用户805533698035 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner5 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz10 天前
QML Hello World 入门示例
qt
xcyxiner13 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner14 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner14 天前
DicomViewer (添加模型类)3
qt
xcyxiner15 天前
DicomViewer (目录调整) 2
qt
xcyxiner15 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00617 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术17 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript