Qt颜色选择器(HSV)

目录

  • 引言
  • HSV颜色空间
  • 实现思路
  • 代码实现

引言

基于Qt实现的HSV颜色选择器,效果如下:
Qt颜色选择器(HSV)_第1张图片

HSV颜色空间

HSV(Hue, Saturation, Value)是根据颜色的直观特征创建的颜色模型,如下图所示:
Qt颜色选择器(HSV)_第2张图片
Hue为色调,通过角度表示不同的颜色,取值范围是0°到359°。Saturation为饱和度,取值范围是0到1。Value为明度,表示颜色明暗,值越大越亮,取值范围是0到1。下述实现方案中,饱和度和明度的范围改为0到255。

实现思路

Qt颜色选择器(HSV)_第3张图片
按照上述模型,实现的关键就是将圆锥的三维投影到二维平面,右侧滑动条表示色调Hue,从0到359。中间画布横坐标表示饱和度Saturation,从0到255,纵坐标表示明度Value,从0到255。滑动条通过修改样式表实现,画布通过paintEvent实现。

代码实现

详细代码如下:

class ColorHueBar : public QWidget
{
    Q_OBJECT

public:
    ColorHueBar(QWidget *parent = nullptr);
    ~ColorHueBar();

    void setValue(int val);
    int value();

signals:
    void sig_valueChanged(int val);

private:
    QSlider* m_slider;
};

class ColorSVCanvas : public QWidget
{
    Q_OBJECT

public:
    ColorSVCanvas(QWidget *parent = nullptr);
    ~ColorSVCanvas();

    bool setHue(int hue);
    bool setSaturationValue(int saturation, int value);
    bool setSaturationValue(QPoint saturationValue);

    QColor color();
    QRect availabilityRect();
    int margin();

signals:
    void sig_colorChanged(const QColor &color);

protected:
    void paintEvent(QPaintEvent* ev) Q_DECL_OVERRIDE;
    void resizeEvent(QResizeEvent* ev) Q_DECL_OVERRIDE;
    bool eventFilter(QObject* obj, QEvent* ev) Q_DECL_OVERRIDE;

private:
    QPoint valueFromPos(QPoint& pos);
    QPoint posFromValue(QPoint& val);

private:
    int m_margin;
    int m_radius;
    int m_saturationMax;
    int m_valueMax;

    int m_hue;
    QPoint m_pos;
};

class ColorChecker : public QWidget
{
    Q_OBJECT

public:
    ColorChecker(QWidget *parent = nullptr);
    ~ColorChecker();

protected:
    void paintEvent(QPaintEvent* ev) Q_DECL_OVERRIDE;

private:
    int m_checkerSize;
};

class ColorAlphaBar : public QWidget
{
    Q_OBJECT

public:
    ColorAlphaBar(QWidget *parent = nullptr);
    ~ColorAlphaBar();

    void setColor(QColor color);
    QColor color();

    void setValue(int alpha);

signals:
    void sig_colorChanged(const QColor &color);

protected:
    void resizeEvent(QResizeEvent* ev) Q_DECL_OVERRIDE;

private:
    ColorChecker* m_checker;
    QSlider* m_slider;
    QColor m_color;

    int m_grooveHeight;
};

class ColorWorkbench : public QDialog
{
    Q_OBJECT

public:
    ColorWorkbench(QWidget *parent = nullptr);
    ~ColorWorkbench();

    void setColor(QColor color);

signals:
    void sig_colorChanged(const QColor &color);

private:
    QColor colorFromStr(QString str);

private slots:
    void slot_colorDisplay(const QColor &color);
    void slot_colorEdit(const QString &text);

private:
    ColorSVCanvas* m_canvas;
    ColorHueBar* m_hsvBar;
    ColorAlphaBar* m_alphaSlider;
    QLineEdit* m_lineEdit;
    QPushButton* m_clearBtn;
    QPushButton* m_confirmBtn;
};

class ColorPicker : public QLabel
{
    Q_OBJECT

public:
    ColorPicker(QWidget *parent = nullptr);
    ~ColorPicker();

protected:
    void resizeEvent(QResizeEvent* ev) Q_DECL_OVERRIDE;

private:
    void setColor(const QColor &color);

private slots:
    void slot_showPopup();
    void slot_colorChanged(const QColor &color);

private:
    QPushButton* m_button;
    ColorChecker* m_checker;
    ColorWorkbench* m_popup;

    QColor m_curColor;
    QColor m_oriColor;
};
ColorHueBar::ColorHueBar(QWidget *parent)
    : QWidget(parent)
{
    m_slider = new QSlider(this);
    m_slider->setMaximum(359);
    m_slider->setFixedWidth(16);
    m_slider->setStyleSheet("QSlider::groove:vertical {width: 12px; \
                                background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, \
                                    stop:0 hsv(0,255,255), \
                                    stop:0.17 hsv(59,255,255), \
                                    stop:0.33 hsv(119,255,255), \
                                    stop:0.5 hsv(179,255,255), \
                                    stop:0.67 hsv(239,255,255), \
                                    stop:0.83 hsv(299,255,255), \
                                    stop:1 hsv(359,255,255));} \
                            QSlider::handle:vertical { \
                                background: white; \
                                height: 4px; \
                                margin:0px -2px; \
                                border: 1px solid grey; \
                                border-radius: 2px;} \
                            QSlider::add-page:vertical {background: transparent;} \
                            QSlider::sub-page:vertical {background: transparent;}");
    setValue(0);
    connect(m_slider, &QSlider::valueChanged, this, [this]{
        emit sig_valueChanged(value());
    });

    auto mainLayout = new QHBoxLayout(this);
    mainLayout->setMargin(0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(m_slider);
}

ColorHueBar::~ColorHueBar()
{

}

void ColorHueBar::setValue(int val)
{
    m_slider->setValue(qAbs(val - m_slider->maximum()));
}

int ColorHueBar::value()
{
    return qAbs(m_slider->value() - m_slider->maximum());
}

ColorSVCanvas::ColorSVCanvas(QWidget *parent)
    : QWidget(parent)
    , m_margin(5)
    , m_radius(m_margin - 1)
    , m_saturationMax(255)
    , m_valueMax(255)
    , m_hue(0)
    , m_pos(QPoint(-1, -1))
{
    installEventFilter(this);
}

ColorSVCanvas::~ColorSVCanvas()
{

}

bool ColorSVCanvas::setHue(int hue)
{
    if(hue < 0 || hue > 359)
        return false;

    m_hue = hue;

    update();
    emit sig_colorChanged(color());

    return true;
}

bool ColorSVCanvas::setSaturationValue(int saturation, int value)
{
    return setSaturationValue(QPoint(saturation, value));
}

bool ColorSVCanvas::setSaturationValue(QPoint saturationValue)
{
    if(!QRect(0,0,256,256).contains(saturationValue))
        return false;

    m_pos = posFromValue(saturationValue);

    update();
    emit sig_colorChanged(color());

    return true;
}

QColor ColorSVCanvas::color()
{
    QPoint tmpVal = valueFromPos(m_pos);
    QColor tmpColor;
    tmpColor.setHsv(m_hue, tmpVal.x(), tmpVal.y());
    return tmpColor;
}

QRect ColorSVCanvas::availabilityRect()
{
    QRect tmpRect(m_margin, m_margin, width() - m_margin * 2, height() - m_margin * 2);
    return tmpRect;
}

int ColorSVCanvas::margin()
{
    return m_margin;
}

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

    QRect rect = availabilityRect();

    // 绘制从左到右RGB(255,255,255)到HSV(h,255,255)的渐变
    QLinearGradient linearGradientH(rect.topLeft(), rect.topRight());
    linearGradientH.setColorAt(0, Qt::white);
    QColor color;
    color.setHsv(m_hue, m_saturationMax, m_valueMax);
    linearGradientH.setColorAt(1, color);
    painter.fillRect(rect, linearGradientH);

    // 绘制顶部颜色值为RGBA(0,0,0,0)到最底部RGBA(0,0,0,255)的渐变
    QLinearGradient linearGradientV(rect.topLeft(), rect.bottomLeft());
    linearGradientV.setColorAt(0, QColor(0, 0, 0, 0));
    linearGradientV.setColorAt(1, QColor(0, 0, 0, 255));
    painter.fillRect(rect, linearGradientV);

    painter.setPen(QColor(Qt::darkGray));
    painter.drawEllipse(m_pos, m_radius, m_radius);
}

void ColorSVCanvas::resizeEvent(QResizeEvent *)
{
    if(m_pos == QPoint(-1, -1)){
        setSaturationValue(255, 255);
    }
}

bool ColorSVCanvas::eventFilter(QObject *obj, QEvent *ev)
{
    if(obj == this){
        if(ev->type() == QEvent::MouseButtonPress){
            if(availabilityRect().contains(mapFromGlobal(QCursor::pos()))){
                m_pos = mapFromGlobal(QCursor::pos());
                update();
                emit sig_colorChanged(color());
            }
        }
    }
    return QWidget::eventFilter(obj, ev);
}

QPoint ColorSVCanvas::valueFromPos(QPoint &pos)
{
    QRect tmpRect = availabilityRect();

    QPoint tmpPos = pos - tmpRect.topLeft();
    int saturation = tmpPos.x() * m_saturationMax / tmpRect.width();
    int value = qAbs(tmpPos.y() * m_valueMax / tmpRect.height() - m_valueMax);

    return QPoint(saturation, value);
}

QPoint ColorSVCanvas::posFromValue(QPoint &val)
{
    QRect tmpRect = availabilityRect();

    int tmpX = val.x() * tmpRect.width() / m_saturationMax;
    int tmpY = qAbs(val.y() - m_valueMax) * tmpRect.height() / m_valueMax;

    return QPoint(tmpX, tmpY) + tmpRect.topLeft();
}

ColorChecker::ColorChecker(QWidget *parent)
    : QWidget(parent)
    , m_checkerSize(6)
{

}

ColorChecker::~ColorChecker()
{

}

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

    int rowCount = ev->rect().height() / m_checkerSize + 1;
    int colCount = ev->rect().width() / m_checkerSize + 1;

    for(int i=0; i < rowCount; i++){
        for(int j=0; j < colCount; j++){
            QRect checkerRect(m_checkerSize * j, m_checkerSize * i, m_checkerSize, m_checkerSize);

            QColor checkerColor;
            if(((j + i % 2) % 2) == 0){
                checkerColor = Qt::darkGray;
            }else{
                checkerColor = Qt::white;
            }
            painter.fillRect(checkerRect, checkerColor);
        }
    }
}

ColorAlphaBar::ColorAlphaBar(QWidget *parent)
    : QWidget(parent)
    , m_grooveHeight(12)
{
    m_checker = new ColorChecker(this);

    m_slider = new QSlider(Qt::Horizontal, this);
    m_slider->setMaximum(255);
    m_slider->setValue(m_slider->maximum());
    m_slider->setFixedHeight(16);
    setColor(Qt::red);

    connect(m_slider, &QSlider::valueChanged, this, [this]{
        emit sig_colorChanged(color());
    });

    auto mainLayout = new QVBoxLayout(this);
    mainLayout->setMargin(0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(m_slider);
}

ColorAlphaBar::~ColorAlphaBar()
{

}

void ColorAlphaBar::setColor(QColor oriColor)
{
    m_color = oriColor;
    m_color.setAlpha(255);

    QColor tmpColor(oriColor);
    tmpColor.setAlpha(0);

    m_slider->setStyleSheet(QString("QSlider::groove:horizontal { \
                                        height: %3px; \
                                        background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, \
                                        stop:0 %1, \
                                        stop:1 %2);} \
                                    QSlider::handle:horizontal { \
                                        background: white; \
                                        width: 4px; \
                                        margin: -2px 0; \
                                        border: 1px solid grey; \
                                        border-radius: 2px;} \
                                    QSlider::add-page:horizontal {background: transparent;} \
                                    QSlider::sub-page:horizontal {background: transparent;}")
                                    .arg(tmpColor.name(QColor::HexArgb))
                                    .arg(m_color.name())
                                    .arg(m_grooveHeight));

    emit sig_colorChanged(color());
}

QColor ColorAlphaBar::color()
{
    QColor tmpColor(m_color);
    tmpColor.setAlpha(m_slider->value());
    return tmpColor;
}

void ColorAlphaBar::setValue(int alpha)
{
    m_slider->setValue(alpha);
}

void ColorAlphaBar::resizeEvent(QResizeEvent *)
{
    m_checker->setGeometry(0, (height() - m_grooveHeight) / 2, width(), m_grooveHeight);
}

ColorWorkbench::ColorWorkbench(QWidget *parent)
    : QDialog(parent, Qt::Popup)
{
    setFixedSize(320, 250);
    setAttribute(Qt::WA_StyledBackground);
    setObjectName("workbench");
    setStyleSheet("#workbench{background-color:white; border:1px solid rgb(245,245,245); border-radius: 6px;}");

    m_canvas = new ColorSVCanvas(this);
    m_canvas->setFixedSize(280, 180);

    m_hsvBar = new ColorHueBar(this);
    m_hsvBar->setFixedHeight(m_canvas->availabilityRect().height());

    m_alphaSlider = new ColorAlphaBar(this);
    m_alphaSlider->setFixedWidth(m_canvas->availabilityRect().width());

    m_lineEdit = new QLineEdit(this);

    m_clearBtn = new QPushButton("clear",this);
    m_clearBtn->setVisible(false);

    m_confirmBtn = new QPushButton("confirm", this);
    connect(m_confirmBtn, &QPushButton::clicked, this, [this]{
        accept();
    });

    connect(m_hsvBar, &ColorHueBar::sig_valueChanged, this, [this](int val){
        m_canvas->setHue(val);
    });
    connect(m_canvas, &ColorSVCanvas::sig_colorChanged, this, [this](const QColor &color){
        m_alphaSlider->setColor(color);
    });
    connect(m_alphaSlider, &ColorAlphaBar::sig_colorChanged, this, &ColorWorkbench::slot_colorDisplay);
    connect(m_lineEdit, &QLineEdit::textEdited, this, &ColorWorkbench::slot_colorEdit);

    // 布局
    auto handleLayout = new QHBoxLayout;
    handleLayout->addSpacing(m_canvas->margin());
    handleLayout->addWidget(m_lineEdit);
    handleLayout->addStretch();
    handleLayout->addWidget(m_clearBtn);
    handleLayout->addWidget(m_confirmBtn);

    auto alphaLayout = new QHBoxLayout;
    alphaLayout->setMargin(0);
    alphaLayout->addSpacing(m_canvas->margin());
    alphaLayout->addWidget(m_alphaSlider);

    auto mainLayout = new QGridLayout(this);
    mainLayout->addWidget(m_canvas, 0, 0);
    mainLayout->addWidget(m_hsvBar, 0, 1);
    mainLayout->addLayout(alphaLayout, 1, 0);
    mainLayout->addLayout(handleLayout, 2, 0, 1, 2);
}

ColorWorkbench::~ColorWorkbench()
{

}

void ColorWorkbench::setColor(QColor color)
{
    m_hsvBar->setValue(color.hsvHue());
    m_canvas->setSaturationValue(color.hsvSaturation(), color.value());
    m_alphaSlider->setValue(color.alpha());
}

QColor ColorWorkbench::colorFromStr(QString str)
{
    QColor color(str);
    if(!color.isValid()){
        QString tmpStr = str;
        QRegExp rx("[^\\d+^,^.]");
        if(str.contains(rx)){
            tmpStr.remove(rx);
        }
        if(!tmpStr.isEmpty()){
            QStringList strList = tmpStr.split(",");
            if(str.contains("rgba") && strList.count() == 4){
                color.setRgb(strList.at(0).toInt(), strList.at(1).toInt(), strList.at(2).toInt());
                if(strList.at(3).toDouble()>1){
                    color.setAlpha(strList.at(3).toInt());
                }else{
                    color.setAlphaF(strList.at(3).toDouble());
                }
            }
            else if(str.contains("rgb") && strList.count() == 3){
                color.setRgb(strList.at(0).toInt(), strList.at(1).toInt(), strList.at(2).toInt());
            }
            else if(str.contains("hsv") && strList.count() == 3){
                color.setHsv(strList.at(0).toInt(), strList.at(1).toInt(), strList.at(2).toInt());
            }
        }
    }

    return color;
}

void ColorWorkbench::slot_colorDisplay(const QColor& color)
{
    QRegExp reg("(\\.){0,1}0+$");// 去除末尾0
    m_lineEdit->setText(QString("rgba(%1, %2, %3, %4)")
                        .arg(color.red())
                        .arg(color.green())
                        .arg(color.blue())
                        .arg(QString::number(color.alphaF(), 'f', 2).replace(reg, "")));

    emit sig_colorChanged(color);
}

void ColorWorkbench::slot_colorEdit(const QString &text)
{
    QColor color = colorFromStr(text);
    if(color.isValid()){
        disconnect(m_alphaSlider, &ColorAlphaBar::sig_colorChanged, this, &ColorWorkbench::slot_colorDisplay);
        setColor(color);
        connect(m_alphaSlider, &ColorAlphaBar::sig_colorChanged, this, &ColorWorkbench::slot_colorDisplay);

        emit sig_colorChanged(color);
    }
}

ColorPicker::ColorPicker(QWidget *parent)
    : QLabel(parent)
{
    m_checker = new ColorChecker(this);

    m_popup = new ColorWorkbench(this);
    connect(m_popup, &ColorWorkbench::sig_colorChanged, this, &ColorPicker::slot_colorChanged);
    connect(m_popup, &QDialog::finished, this, [this](int result){
        if(result == QDialog::Accepted){
            m_oriColor = m_curColor;
        }
        else{
            setColor(m_oriColor);
        }
    });

    m_button = new QPushButton("v", this);
    m_button->setFixedSize(30, 30);
    connect(m_button, &QPushButton::pressed, this, &ColorPicker::slot_showPopup);

    setFixedSize(40, 40);
    setStyleSheet(QString("QLabel{border:1px solid %1; border-radius: 4px; background-color: %2;}")
                  .arg(QColor(230, 230, 230).name())
                  .arg(QColor(Qt::white).name()));
    setColor(QColor(255,0,0,150));

    auto mainLayout = new QHBoxLayout(this);
    mainLayout->setMargin(0);
    mainLayout->addWidget(m_button);
}

ColorPicker::~ColorPicker()
{

}

void ColorPicker::resizeEvent(QResizeEvent *)
{
    m_checker->setGeometry(m_button->geometry());
}

void ColorPicker::setColor(const QColor &color)
{
    m_curColor = color;
    m_button->setStyleSheet(QString("QPushButton{border:1px solid %1; background-color:%2}")
                            .arg(QColor(152,152,152).name())
                            .arg(color.name(QColor::HexArgb)));
}

void ColorPicker::slot_showPopup()
{
    m_oriColor = m_curColor;
    m_popup->setColor(m_oriColor);

    QPoint tmpPos = mapToGlobal(m_button->geometry().center());
    tmpPos += QPoint(-m_popup->width() / 2, m_button->height() / 2 + 5);
    m_popup->move(tmpPos);

    m_popup->open();
}

void ColorPicker::slot_colorChanged(const QColor &color)
{
    if (m_popup->isVisible()) {
        setColor(color);
    }
}

你可能感兴趣的:(qt,c++,qt)