diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp index 043f6f6dcd..76ecb17c41 100644 --- a/libs/widgets/KisVisualColorSelector.cpp +++ b/libs/widgets/KisVisualColorSelector.cpp @@ -1,413 +1,582 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelector.h" #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "KisVisualColorSelectorShape.h" #include "KisVisualRectangleSelectorShape.h" #include "KisVisualTriangleSelectorShape.h" #include "KisVisualEllipticalSelectorShape.h" struct KisVisualColorSelector::Private { KoColor currentcolor; const KoColorSpace *currentCS {0}; QList widgetlist; bool updateSelf {false}; bool updateLonesome {false}; // currently redundant; remove? bool circular {false}; + bool exposureSupported = false; + bool isRGBA = false; + int displayPosition[4]; // map channel index to storage index for display + int colorChannelCount; + QVector4D channelValues; + ColorModel model; const KoColorDisplayRendererInterface *displayRenderer {0}; KisColorSelectorConfiguration acs_config; KisSignalCompressor *updateTimer {0}; }; KisVisualColorSelector::KisVisualColorSelector(QWidget *parent) : KisColorSelectorInterface(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection); } KisVisualColorSelector::~KisVisualColorSelector() { delete m_d->updateTimer; } void KisVisualColorSelector::slotSetColor(const KoColor &c) { m_d->currentcolor = c; if (m_d->currentCS != c.colorSpace()) { slotsetColorSpace(c.colorSpace()); } - updateSelectorElements(QObject::sender()); + else { + m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); + Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { + shape->setChannelValues(m_d->channelValues, true); + } + } } void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs) { if (m_d->currentCS != cs) { m_d->currentCS = cs; slotRebuildSelectors(); } } void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate) { m_d->circular = forceCircular; m_d->updateLonesome = forceSelfUpdate; } KoColor KisVisualColorSelector::getCurrentColor() const { return m_d->currentcolor; } +QVector4D KisVisualColorSelector::getChannelValues() const +{ + return m_d->channelValues; +} + +KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const +{ + KoColor c(m_d->currentCS); + QVector4D baseValues(coordinates); + QVector channelValues(c.colorSpace()->channelCount()); + channelValues.fill(1.0); + + if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { + + if (m_d->model == ColorModel::HSV) { + HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); + } + else if (m_d->model == ColorModel::HSL) { + HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); + } + else if (m_d->model == ColorModel::HSI) { + // why suddenly qreal? + qreal temp[3]; + HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]); + baseValues.setX(temp[0]); + baseValues.setY(temp[1]); + baseValues.setZ(temp[2]); + } + else /*if (m_d->model == ColorModel::HSY)*/ { + QVector luma= m_d->currentCS->lumaCoefficients(); + qreal temp[3]; + HSYToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2], + luma[0], luma[1], luma[2]); + baseValues.setX(temp[0]); + baseValues.setY(temp[1]); + baseValues.setZ(temp[2]); + } + } + + for (int i=0; icolorChannelCount; i++) { + // TODO: proper exposure control + channelValues[m_d->displayPosition[i]] = baseValues[i] /* *(maxvalue[i]) */; + } + + c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues); + + return c; + +} + +QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const +{ + if (c.colorSpace() != m_d->currentCS) { + c.convertTo(m_d->currentCS); + } + QVector channelValues (c.colorSpace()->channelCount()); + channelValues.fill(1.0); + m_d->currentCS->normalisedChannelsValue(c.data(), channelValues); + QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0); + // TODO: proper exposure control + // TODO: L*a*b is apparently not [0, 1]^3 as "normalized" values, needs extra transform (old bug) + for (int i =0; icolorChannelCount; i++) { + channelValuesDisplay[i] = qBound(0.f, channelValues[m_d->displayPosition[i]], 1.f); + } + if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { + if (m_d->isRGBA == true) { + if (m_d->model == ColorModel::HSV){ + QVector3D hsv; + // TODO: handle undefined hue case (returns -1) + RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]); + hsv[0] /= 360; + coordinates = QVector4D(hsv, 0.f); + } else if (m_d->model == ColorModel::HSL) { + QVector3D hsl; + RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]); + hsl[0] /= 360; + coordinates = QVector4D(hsl, 0.f); + } else if (m_d->model == ColorModel::HSI) { + qreal hsi[3]; + RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]); + coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f); + } else if (m_d->model == ColorModel::HSY) { + QVector luma = m_d->currentCS->lumaCoefficients(); + qreal hsy[3]; + RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], luma[0], luma[1], luma[2]); + coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f); + } + } + } else { + for (int i=0; i<4; i++) + { + coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f); + } + } + return coordinates; +} + void KisVisualColorSelector::configurationChanged() { if (m_d->updateTimer) { m_d->updateTimer->start(); } } void KisVisualColorSelector::slotRebuildSelectors() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); + QList channelList = m_d->currentCS->channels(); + int cCount = 0; + Q_FOREACH(const KoChannelInfo *channel, channelList) + { + if (channel->channelType() != KoChannelInfo::ALPHA) + { + m_d->displayPosition[cCount] = channel->displayPosition(); + ++cCount; + } + } + Q_ASSERT_X(cCount < 5, "", "unsupported channel count!"); + m_d->colorChannelCount = cCount; + + // TODO: The following is done because the IDs are actually strings. Ideally, in the future, we + // refactor everything so that the IDs are actually proper enums or something faster. + if (m_d->displayRenderer + && (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID + || m_d->currentCS->colorDepthId() == Float32BitsColorDepthID + || m_d->currentCS->colorDepthId() == Float64BitsColorDepthID) + && m_d->currentCS->colorModelId() != LABAColorModelID + && m_d->currentCS->colorModelId() != CMYKAColorModelID) { + m_d->exposureSupported = true; + } else { + m_d->exposureSupported = false; + } + if (m_d->currentCS->colorModelId() == RGBAColorModelID) { + m_d->isRGBA = true; + } else { + m_d->isRGBA = false; + } + qDeleteAll(children()); m_d->widgetlist.clear(); // TODO: Layout only used for monochrome selector currently, but always present QLayout *layout = new QHBoxLayout; //recreate all the widgets. if (m_d->currentCS->colorChannelCount() == 1) { KisVisualColorSelectorShape *bar; if (m_d->circular==false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, 20); bar->setMaximumWidth(width()*0.1); bar->setMaximumHeight(height()); } else { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); layout->setMargin(0); } - connect (bar, SIGNAL(sigNewColor(KoColor)), this, SLOT(updateFromWidgets(KoColor))); + connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); layout->addWidget(bar); m_d->widgetlist.append(bar); } else if (m_d->currentCS->colorChannelCount() == 3) { KisVisualColorSelectorShape::ColorModel modelS = KisVisualColorSelectorShape::HSV; int channel1 = 0; int channel2 = 1; int channel3 = 2; switch(m_d->acs_config.subTypeParameter) { case KisColorSelectorConfiguration::H: channel1 = 0; break; case KisColorSelectorConfiguration::hsyS: case KisColorSelectorConfiguration::hsiS: case KisColorSelectorConfiguration::hslS: case KisColorSelectorConfiguration::hsvS: channel1 = 1; break; case KisColorSelectorConfiguration::V: case KisColorSelectorConfiguration::L: case KisColorSelectorConfiguration::I: case KisColorSelectorConfiguration::Y: channel1 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter"); } switch(m_d->acs_config.mainTypeParameter) { case KisColorSelectorConfiguration::hsySH: modelS = KisVisualColorSelectorShape::HSY; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsiSH: modelS = KisVisualColorSelectorShape::HSI; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hslSH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsvSH: modelS = KisVisualColorSelectorShape::HSV; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::YH: modelS = KisVisualColorSelectorShape::HSY; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::LH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::IH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::VH: modelS = KisVisualColorSelectorShape::HSV; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::SY: modelS = KisVisualColorSelectorShape::HSY; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SI: modelS = KisVisualColorSelectorShape::HSI; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SL: modelS = KisVisualColorSelectorShape::HSL; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SV: case KisColorSelectorConfiguration::SV2: modelS = KisVisualColorSelectorShape::HSV; channel2 = 1; channel3 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter"); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { modelS = KisVisualColorSelectorShape::HSV; //Triangle only really works in HSV mode. } + // TODO: properly relocate enum definition + m_d->model = static_cast(modelS); KisVisualColorSelectorShape *bar; if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); } else { // Accessing bar below would crash since it's not initialized. // Hopefully this can never happen. warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape"; Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape"); return; } - bar->setColor(m_d->currentcolor); m_d->widgetlist.append(bar); KisVisualColorSelectorShape *block; if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else { block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } - block->setColor(m_d->currentcolor); - connect (bar, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); - connect (block, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); - connect (bar, SIGNAL(sigHSXchange()), SLOT(HSXwrangler())); - connect (block, SIGNAL(sigHSXchange()), SLOT(HSXwrangler())); + connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); + connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); } else if (m_d->currentCS->colorChannelCount() == 4) { KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 1); KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 2, 3); - connect (block, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); - connect (block2, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); + connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); + connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); m_d->widgetlist.append(block2); } this->setLayout(layout); // make sure we call "our" resize function KisVisualColorSelector::resizeEvent(0); + + // finally recalculate channel values and update widgets + m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); + Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { + shape->setChannelValues(m_d->channelValues, true); + } } void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { m_d->displayRenderer = displayRenderer; if (m_d->widgetlist.size()>0) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setDisplayRenderer(displayRenderer); } } } void KisVisualColorSelector::updateSelectorElements(QObject *source) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { if (shape!=source) { if (m_d->updateSelf) { shape->setColorFromSibling(m_d->currentcolor); } else { shape->setColor(m_d->currentcolor); } } else if(!m_d->updateSelf) { shape->setColor(m_d->currentcolor); } } } void KisVisualColorSelector::updateFromWidgets(KoColor c) { m_d->currentcolor = c; m_d->updateSelf = true; updateSelectorElements(QObject::sender()); Q_EMIT sigNewColor(c); } +void KisVisualColorSelector::slotCursorMoved(QPointF pos) +{ + const KisVisualColorSelectorShape *shape = qobject_cast(sender()); + Q_ASSERT(shape); + QVector channels = shape->getChannels(); + m_d->channelValues[channels.at(0)] = pos.x(); + if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) + { + m_d->channelValues[channels.at(1)] = pos.y(); + } + KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues); + if (newColor != m_d->currentcolor) + { + m_d->currentcolor = newColor; + + Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { + if (widget != shape){ + widget->setChannelValues(m_d->channelValues, false); + } + } + emit sigNewColor(m_d->currentcolor); + } +} + void KisVisualColorSelector::leaveEvent(QEvent *) { m_d->updateSelf = false; } void KisVisualColorSelector::resizeEvent(QResizeEvent *) { int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); QRect newrect(0,0, this->geometry().width(), this->geometry().height()); if (!m_d->currentCS) { slotsetColorSpace(m_d->currentcolor.colorSpace()); } if (m_d->currentCS->colorChannelCount()==3) { if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) { m_d->widgetlist.at(0)->setMaximumWidth(borderWidth); m_d->widgetlist.at(0)->setMinimumWidth(borderWidth); m_d->widgetlist.at(0)->setMinimumHeight(sizeValue); m_d->widgetlist.at(0)->setMaximumHeight(sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } m_d->widgetlist.at(0)->setBorderWidth(borderWidth); if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect)); } } else if (m_d->currentCS->colorChannelCount() == 4) { int sizeBlock = qMin(width()/2 - 8, height()); m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock); m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock); } Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->update(); } } void KisVisualColorSelector::HSXwrangler() { //qDebug() << this << "HSXWrangler"; QVector currentCoordinates = QVector(3); QVector w1 = m_d->widgetlist.at(0)->getHSX(currentCoordinates, true); QVector w2 = m_d->widgetlist.at(1)->getHSX(currentCoordinates, true); QVector ch(3); ch[0] = m_d->widgetlist.at(0)->getChannels().at(0); ch[1] = m_d->widgetlist.at(1)->getChannels().at(0); ch[2] = m_d->widgetlist.at(1)->getChannels().at(1); currentCoordinates[ch[0]] = w1[ch[0]]; currentCoordinates[ch[1]] = w2[ch[1]]; currentCoordinates[ch[2]] = w2[ch[2]]; m_d->widgetlist.at(0)->setHSX(currentCoordinates, true); m_d->widgetlist.at(1)->setHSX(currentCoordinates, true); } diff --git a/libs/widgets/KisVisualColorSelector.h b/libs/widgets/KisVisualColorSelector.h index 04ad5eebd6..6a5a6d37ab 100644 --- a/libs/widgets/KisVisualColorSelector.h +++ b/libs/widgets/KisVisualColorSelector.h @@ -1,86 +1,91 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_COLOR_SELECTOR_H #define KIS_VISUAL_COLOR_SELECTOR_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisColorSelectorInterface.h" #include "kritawidgets_export.h" /** * @brief The KisVisualColorSelector class * * This gives a color selector box that draws gradients and everything. * * Unlike other color selectors, this one draws the full gamut of the given * colorspace. */ class KRITAWIDGETS_EXPORT KisVisualColorSelector : public KisColorSelectorInterface { Q_OBJECT public: + enum ColorModel{Channel, HSV, HSL, HSI, HSY, YUV}; explicit KisVisualColorSelector(QWidget *parent = 0); ~KisVisualColorSelector() override; /** * @brief setConfig * @param forceCircular * Force circular is for space where you only have room for a circular selector. * @param forceSelfUpdate * force self-update is for making it update itself when using a modal dialog. */ void setConfig(bool forceCircular, bool forceSelfUpdate) override; KoColor getCurrentColor() const override; + QVector4D getChannelValues() const; + KoColor convertShapeCoordsToKoColor(const QVector4D &coordinates) const; + QVector4D convertKoColorToShapeCoordinates(KoColor c) const; public Q_SLOTS: void slotSetColor(const KoColor &c) override; void slotsetColorSpace(const KoColorSpace *cs); void slotRebuildSelectors(); void configurationChanged(); void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) override; private Q_SLOTS: void updateFromWidgets(KoColor c); + void slotCursorMoved(QPointF pos); void HSXwrangler(); protected: void leaveEvent(QEvent *) override; void resizeEvent(QResizeEvent *) override; private: struct Private; const QScopedPointer m_d; void updateSelectorElements(QObject *source); void drawGradients(); }; #endif diff --git a/libs/widgets/KisVisualColorSelectorShape.cpp b/libs/widgets/KisVisualColorSelectorShape.cpp index 715a216cf1..f343801808 100644 --- a/libs/widgets/KisVisualColorSelectorShape.cpp +++ b/libs/widgets/KisVisualColorSelectorShape.cpp @@ -1,621 +1,660 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelectorShape.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" struct KisVisualColorSelectorShape::Private { QImage gradient; QImage fullSelector; bool imagesNeedUpdate {true}; - QPointF currentCoordinates; + QPointF currentCoordinates; // somewhat redundant? + QVector4D currentChannelValues; Dimensions dimension; ColorModel model; const KoColorSpace *colorSpace; - KoColor currentColor; + KoColor currentColor;// TODO: relocate to parent int channel1; int channel2; - KisSignalCompressor *updateTimer {0}; + KisSignalCompressor *updateTimer {0}; // To be removed bool mousePressActive = false; const KoColorDisplayRendererInterface *displayRenderer = 0; - qreal hue = 0.0; - qreal sat = 0.0; - qreal tone = 0.0; + qreal hue = 0.0; // To be removed + qreal sat = 0.0; // To be removed + qreal tone = 0.0; // To be removed bool usesOCIO = false; bool isRGBA = false; bool is8Bit = false; }; KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent, KisVisualColorSelectorShape::Dimensions dimension, KisVisualColorSelectorShape::ColorModel model, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private) { m_d->dimension = dimension; m_d->model = model; m_d->colorSpace = cs; // TODO: The following is done because the IDs are actually strings. Ideally, in the future, we // refactor everything so that the IDs are actually proper enums or something faster. if (m_d->displayRenderer && (m_d->colorSpace->colorDepthId() == Float16BitsColorDepthID || m_d->colorSpace->colorDepthId() == Float32BitsColorDepthID || m_d->colorSpace->colorDepthId() == Float64BitsColorDepthID) && m_d->colorSpace->colorModelId() != LABAColorModelID && m_d->colorSpace->colorModelId() != CMYKAColorModelID) { m_d->usesOCIO = true; } else { m_d->usesOCIO = false; } if (m_d->colorSpace->colorModelId() == RGBAColorModelID) { m_d->isRGBA = true; } else { m_d->isRGBA = false; } if (m_d->colorSpace->colorDepthId() == Integer8BitsColorDepthID) { m_d->is8Bit = true; } else { m_d->is8Bit = false; } m_d->currentColor = KoColor(); m_d->currentColor.setOpacity(1.0); m_d->currentColor.convertTo(cs); int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // HACK: the updateTimer isn't connected to anything, we only check whether it's still active // and running in order to determine whether we will emit a certain signal. - m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); + //m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); setDisplayRenderer(displayRenderer); show(); } KisVisualColorSelectorShape::~KisVisualColorSelectorShape() { } void KisVisualColorSelectorShape::updateCursor() { QPointF point1 = convertKoColorToShapeCoordinate(m_d->currentColor); if (point1 != m_d->currentCoordinates) { m_d->currentCoordinates = point1; } } QPointF KisVisualColorSelectorShape::getCursorPosition() { return m_d->currentCoordinates; } +void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal) +{ + QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0)); + if (newPos != m_d->currentCoordinates) + { + m_d->currentCoordinates = newPos; + // for internal consistency, because we have a bit of redundancy here + m_d->currentChannelValues[m_d->channel1] = newPos.x(); + if (m_d->dimension == Dimensions::twodimensional){ + m_d->currentChannelValues[m_d->channel2] = newPos.y(); + } + update(); + if (signal){ + emit sigCursorMoved(newPos); + } + } +} + +void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor) +{ + //qDebug() << this << "setChannelValues"; + m_d->currentChannelValues = channelValues; + if (setCursor) { + m_d->currentCoordinates = QPointF(channelValues[m_d->channel1], channelValues[m_d->channel2]); + } + else { + // for internal consistency, because we have a bit of redundancy here + m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x(); + if (m_d->dimension == Dimensions::twodimensional){ + m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y(); + } + } + m_d->imagesNeedUpdate = true; + update(); +} + void KisVisualColorSelectorShape::setColor(KoColor c) { //qDebug() << this << "KisVisualColorSelectorShape::setColor"; if (c.colorSpace() != m_d->colorSpace) { c.convertTo(m_d->colorSpace); } m_d->currentColor = c; updateCursor(); m_d->imagesNeedUpdate = true; update(); } void KisVisualColorSelectorShape::setColorFromSibling(KoColor c) { //qDebug() << this << "setColorFromSibling"; if (c.colorSpace() != m_d->colorSpace) { c.convertTo(m_d->colorSpace); } m_d->currentColor = c; m_d->imagesNeedUpdate = true; update(); } void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_d->displayRenderer) { m_d->displayRenderer->disconnect(this); } m_d->displayRenderer = displayRenderer; } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(updateFromChangedDisplayRenderer()), Qt::UniqueConnection); } void KisVisualColorSelectorShape::updateFromChangedDisplayRenderer() { //qDebug() << this << "updateFromChangedDisplayRenderer();"; m_d->imagesNeedUpdate = true; - updateCursor(); + //updateCursor(); //m_d->currentColor = convertShapeCoordinateToKoColor(getCursorPosition()); update(); } void KisVisualColorSelectorShape::forceImageUpdate() { //qDebug() << this << "forceImageUpdate"; m_d->imagesNeedUpdate = true; } QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){ QColor col; KoColor color = c; if (m_d->displayRenderer) { color.convertTo(m_d->displayRenderer->getPaintingColorSpace()); col = m_d->displayRenderer->toQColor(c); } else { col = c.toQColor(); } return col; } +// currently unused? void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2) { //qDebug() << this << "slotSetActiveChannels"; int maxchannel = m_d->colorSpace->colorChannelCount()-1; m_d->channel1 = qBound(0, channel1, maxchannel); m_d->channel2 = qBound(0, channel2, maxchannel); m_d->imagesNeedUpdate = true; update(); } bool KisVisualColorSelectorShape::imagesNeedUpdate() const { return m_d->imagesNeedUpdate; } QImage KisVisualColorSelectorShape::getImageMap() { //qDebug() << this << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate; + const KisVisualColorSelector *selector = qobject_cast(parent()); if (m_d->imagesNeedUpdate == true) { // Fill a buffer with the right kocolors - quint8 *data = new quint8[width() * height() * m_d->currentColor.colorSpace()->pixelSize()]; + quint8 *data = new quint8[width() * height() * m_d->colorSpace->pixelSize()]; quint8 *dataPtr = data; + QVector4D coordinates = m_d->currentChannelValues; for (int y = 0; y < height(); y++) { for (int x=0; x < width(); x++) { QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPoint(x, y)); - KoColor c = convertShapeCoordinateToKoColor(newcoordinate); - memcpy(dataPtr, c.data(), m_d->currentColor.colorSpace()->pixelSize()); - dataPtr += m_d->currentColor.colorSpace()->pixelSize(); + coordinates[m_d->channel1] = newcoordinate.x(); + if (m_d->dimension == Dimensions::twodimensional){ + coordinates[m_d->channel2] = newcoordinate.y(); + } + KoColor c = selector->convertShapeCoordsToKoColor(coordinates); + memcpy(dataPtr, c.data(), m_d->colorSpace->pixelSize()); + dataPtr += m_d->colorSpace->pixelSize(); } } // Convert the buffer to a qimage if (m_d->displayRenderer) { - m_d->gradient = m_d->displayRenderer->convertToQImage(m_d->currentColor.colorSpace(), data, width(), height()); + m_d->gradient = m_d->displayRenderer->convertToQImage(m_d->colorSpace, data, width(), height()); } else { - m_d->gradient = m_d->currentColor.colorSpace()->convertToQImage(data, width(), height(), 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); + m_d->gradient = m_d->colorSpace->convertToQImage(data, width(), height(), 0, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } delete[] data; m_d->imagesNeedUpdate = false; // safeguard: if (m_d->gradient.isNull()) { m_d->gradient = QImage(width(), height(), QImage::Format_ARGB32); m_d->gradient.fill(Qt::black); } } return m_d->gradient; } KoColor KisVisualColorSelectorShape::convertShapeCoordinateToKoColor(QPointF coordinates, bool cursor) { //qDebug() << this << ">>>>>>>>> convertShapeCoordinateToKoColor()" << coordinates; KoColor c = m_d->currentColor; QVector channelValues (c.colorSpace()->channelCount()); channelValues.fill(1.0); c.colorSpace()->normalisedChannelsValue(c.data(), channelValues); QVector channelValuesDisplay = channelValues; QVector maxvalue(c.colorSpace()->channelCount()); maxvalue.fill(1.0); if (m_d->usesOCIO == true) { for (int ch = 0; ch < maxvalue.size(); ch++) { KoChannelInfo *channel = m_d->colorSpace->channels()[ch]; maxvalue[ch] = m_d->displayRenderer->maxVisibleFloatValue(channel); channelValues[ch] = channelValues[ch]/(maxvalue[ch]); channelValuesDisplay[KoChannelInfo::displayPositionToChannelIndex(ch, m_d->colorSpace->channels())] = channelValues[ch]; } } else { for (int i =0; i < channelValues.size();i++) { channelValuesDisplay[KoChannelInfo::displayPositionToChannelIndex(i, m_d->colorSpace->channels())] = qBound((float)0.0,channelValues[i], (float)1.0); } } qreal huedivider = 1.0; qreal huedivider2 = 1.0; if (m_d->channel1 == 0) { huedivider = 360.0; } if (m_d->channel2 == 0) { huedivider2 = 360.0; } if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->model == ColorModel::HSV) { /* * RGBToHSV has a undefined hue possibility. This means that hue will be -1. * This can be annoying for dealing with a selector, but I understand it is being * used for the KoColorSelector... For now implement a qMax here. */ QVector inbetween(3); RGBToHSV(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = convertvectorqrealTofloat(getHSX(convertvectorfloatToqreal(inbetween))); inbetween[m_d->channel1] = coordinates.x()*huedivider; if (m_d->dimension == Dimensions::twodimensional) { inbetween[m_d->channel2] = coordinates.y()*huedivider2; } if (cursor) { setHSX(convertvectorfloatToqreal(inbetween)); Q_EMIT sigHSXchange(); } HSVToRGB(qMax(inbetween[0],(float)0.0), inbetween[1], inbetween[2], &channelValuesDisplay[0], &channelValuesDisplay[1], &channelValuesDisplay[2]); } else if (m_d->model == ColorModel::HSL) { /* * HSLToRGB can give negative values on the grey. I fixed the fromNormalisedChannel function to clamp, * but you might want to manually clamp for floating point values. */ QVector inbetween(3); RGBToHSL(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = convertvectorqrealTofloat(getHSX(convertvectorfloatToqreal(inbetween))); inbetween[m_d->channel1] = fmod(coordinates.x()*huedivider, 360.0); if (m_d->dimension == Dimensions::twodimensional) { inbetween[m_d->channel2] = coordinates.y()*huedivider2; } if (cursor) { setHSX(convertvectorfloatToqreal(inbetween)); Q_EMIT sigHSXchange(); } HSLToRGB(qMax(inbetween[0], (float)0.0), inbetween[1], inbetween[2], &channelValuesDisplay[0], &channelValuesDisplay[1], &channelValuesDisplay[2]); } else if (m_d->model == ColorModel::HSI) { /* * HSI is a modified HSY function. */ QVector chan2 = convertvectorfloatToqreal(channelValuesDisplay); QVector inbetween(3); RGBToHSI(chan2[0],chan2[1], chan2[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = getHSX(inbetween); inbetween[m_d->channel1] = coordinates.x(); if (m_d->dimension == Dimensions::twodimensional) { inbetween[m_d->channel2] = coordinates.y(); } if (cursor) { setHSX(inbetween); Q_EMIT sigHSXchange(); } HSIToRGB(inbetween[0], inbetween[1], inbetween[2],&chan2[0],&chan2[1], &chan2[2]); channelValuesDisplay = convertvectorqrealTofloat(chan2); } else /*if (m_d->model == ColorModel::HSY)*/ { /* * HSY is pretty slow to render due being a pretty over-the-top function. * Might be worth investigating whether HCY can be used instead, but I have had * some weird results with that. */ QVector luma= m_d->colorSpace->lumaCoefficients(); QVector chan2 = convertvectorfloatToqreal(channelValuesDisplay); QVector inbetween(3); RGBToHSY(chan2[0],chan2[1], chan2[2], &inbetween[0], &inbetween[1], &inbetween[2], luma[0], luma[1], luma[2]); inbetween = getHSX(inbetween); inbetween[m_d->channel1] = coordinates.x(); if (m_d->dimension == Dimensions::twodimensional) { inbetween[m_d->channel2] = coordinates.y(); } if (cursor) { setHSX(inbetween); Q_EMIT sigHSXchange(); } HSYToRGB(inbetween[0], inbetween[1], inbetween[2],&chan2[0],&chan2[1], &chan2[2], luma[0], luma[1], luma[2]); channelValuesDisplay = convertvectorqrealTofloat(chan2); } } else { channelValuesDisplay[m_d->channel1] = coordinates.x(); if (m_d->dimension == Dimensions::twodimensional) { channelValuesDisplay[m_d->channel2] = coordinates.y(); } } for (int i=0; icolorSpace->channels())]*(maxvalue[i]); } c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues); return c; } QPointF KisVisualColorSelectorShape::convertKoColorToShapeCoordinate(KoColor c) { ////qDebug() << this << ">>>>>>>>> convertKoColorToShapeCoordinate()"; if (c.colorSpace() != m_d->colorSpace) { c.convertTo(m_d->colorSpace); } QVector channelValues (m_d->currentColor.colorSpace()->channelCount()); channelValues.fill(1.0); m_d->colorSpace->normalisedChannelsValue(c.data(), channelValues); QVector channelValuesDisplay = channelValues; QVector maxvalue(c.colorSpace()->channelCount()); maxvalue.fill(1.0); if (m_d->usesOCIO == true) { for (int ch = 0; chcolorSpace->channels()[ch]; maxvalue[ch] = m_d->displayRenderer->maxVisibleFloatValue(channel); channelValues[ch] = channelValues[ch]/(maxvalue[ch]); channelValuesDisplay[KoChannelInfo::displayPositionToChannelIndex(ch, m_d->colorSpace->channels())] = channelValues[ch]; } } else { for (int i =0; icolorSpace->channels())] = qBound((float)0.0,channelValues[i], (float)1.0); } } QPointF coordinates(0.0,0.0); qreal huedivider = 1.0; qreal huedivider2 = 1.0; if (m_d->channel1==0) { huedivider = 360.0; } if (m_d->channel2==0) { huedivider2 = 360.0; } if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->isRGBA == true) { if (m_d->model == ColorModel::HSV){ QVector inbetween(3); RGBToHSV(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = convertvectorqrealTofloat(getHSX(convertvectorfloatToqreal(inbetween))); coordinates.setX(inbetween[m_d->channel1]/huedivider); if (m_d->dimension == Dimensions::twodimensional) { coordinates.setY(inbetween[m_d->channel2]/huedivider2); } } else if (m_d->model == ColorModel::HSL) { QVector inbetween(3); RGBToHSL(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = convertvectorqrealTofloat(getHSX(convertvectorfloatToqreal(inbetween))); coordinates.setX(inbetween[m_d->channel1]/huedivider); if (m_d->dimension == Dimensions::twodimensional) { coordinates.setY(inbetween[m_d->channel2]/huedivider2); } } else if (m_d->model == ColorModel::HSI) { QVector chan2 = convertvectorfloatToqreal(channelValuesDisplay); QVector inbetween(3); RGBToHSI(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2]); inbetween = getHSX(inbetween); coordinates.setX(inbetween[m_d->channel1]); if (m_d->dimension == Dimensions::twodimensional) { coordinates.setY(inbetween[m_d->channel2]); } } else if (m_d->model == ColorModel::HSY) { QVector luma = m_d->colorSpace->lumaCoefficients(); QVector chan2 = convertvectorfloatToqreal(channelValuesDisplay); QVector inbetween(3); RGBToHSY(channelValuesDisplay[0],channelValuesDisplay[1], channelValuesDisplay[2], &inbetween[0], &inbetween[1], &inbetween[2], luma[0], luma[1], luma[2]); inbetween = getHSX(inbetween); coordinates.setX(inbetween[m_d->channel1]); if (m_d->dimension == Dimensions::twodimensional) { coordinates.setY(inbetween[m_d->channel2]); } } } } else { coordinates.setX(qBound((float)0.0, channelValuesDisplay[m_d->channel1], (float)1.0)); if (m_d->dimension == Dimensions::twodimensional) { coordinates.setY(qBound((float)0.0, channelValuesDisplay[m_d->channel2], (float)1.0)); } } return coordinates; } QVector KisVisualColorSelectorShape::convertvectorqrealTofloat(QVector real) { QVector vloat(real.size()); for (int i=0; i KisVisualColorSelectorShape::convertvectorfloatToqreal(QVector vloat) { QVector real(vloat.size()); for (int i=0; ibutton()==Qt::LeftButton) { m_d->mousePressActive = true; QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->pos()); - KoColor col = convertShapeCoordinateToKoColor(coordinates, true); - setColor(col); - Q_EMIT sigNewColor(col); - m_d->updateTimer->start(); + setCursorPosition(coordinates, true); } } void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e) { - if (m_d->mousePressActive==true && this->mask().contains(e->pos())) { + if (m_d->mousePressActive==true) { QPointF coordinates = convertWidgetCoordinateToShapeCoordinate(e->pos()); - quint8* oldData = m_d->currentColor.data(); - KoColor col = convertShapeCoordinateToKoColor(coordinates, true); - QRect offsetrect(this->geometry().topLeft() + QPoint(7.0, 7.0), this->geometry().bottomRight() - QPoint(7.0, 7.0)); - if (offsetrect.contains(e->pos()) || (m_d->colorSpace->difference(col.data(), oldData) > 5)) { - setColor(col); - if (!m_d->updateTimer->isActive()) { - Q_EMIT sigNewColor(col); - m_d->updateTimer->start(); - } - } - + setCursorPosition(coordinates, true); } else { e->ignore(); } } -void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *) +void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e) { - m_d->mousePressActive = false; + if (e->button()==Qt::LeftButton) { + m_d->mousePressActive = false; + } } void KisVisualColorSelectorShape::paintEvent(QPaintEvent*) { QPainter painter(this); //check if old and new colors differ. if (m_d->imagesNeedUpdate) { setMask(getMaskMap()); } drawCursor(); painter.drawImage(0,0,m_d->fullSelector); } KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const { return m_d->dimension; } KisVisualColorSelectorShape::ColorModel KisVisualColorSelectorShape::getColorModel() { return m_d->model; } void KisVisualColorSelectorShape::setFullImage(QImage full) { m_d->fullSelector = full; } KoColor KisVisualColorSelectorShape::getCurrentColor() { - return m_d->currentColor; + const KisVisualColorSelector *selector = qobject_cast(parent()); + if (selector) + { + return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues); + } + return KoColor(m_d->colorSpace); } QVector KisVisualColorSelectorShape::getHSX(QVector hsx, bool wrangler) { QVector ihsx = hsx; if (!wrangler){ //Ok, so this docker will not update luminosity if there's not at the least 3% more variation. //This is necessary for 8bit. if (m_d->is8Bit == true){ if (hsx[2]>m_d->tone-0.03 && hsx[2]tone+0.03) { ihsx[2] = m_d->tone; } } else { if (hsx[2]>m_d->tone-0.005 && hsx[2]tone+0.005) { ihsx[2] = m_d->tone; } } if (m_d->model==HSV){ if (hsx[2]<=0.0) { ihsx[1] = m_d->sat; } } else { if ((hsx[2]<=0.0 || hsx[2]>=1.0)) { ihsx[1] = m_d->sat; } } if ((hsx[1]<=0.0 || hsx[0]<0.0)){ ihsx[0]=m_d->hue; } } else { ihsx[0]=m_d->hue; ihsx[1]=m_d->sat; ihsx[2]=m_d->tone; } return ihsx; } void KisVisualColorSelectorShape::setHSX(QVector hsx, bool wrangler) { if (wrangler){ m_d->tone = hsx[2]; m_d->sat = hsx[1]; m_d->hue = hsx[0]; } else { if (m_d->channel1==2 || m_d->channel2==2){ m_d->tone=hsx[2]; } if (m_d->model==HSV){ if (hsx[2]>0.0) { m_d->sat = hsx[1]; } } else { if ((hsx[2]>0.0 || hsx[2]<1.0)) { m_d->sat = hsx[1]; } } if ((hsx[1]>0.0 && hsx[0]>=0.0)){ m_d->hue = hsx[0]; } } } QVector KisVisualColorSelectorShape::getChannels() const { QVector channels(2); channels[0] = m_d->channel1; channels[1] = m_d->channel2; return channels; } diff --git a/libs/widgets/KisVisualColorSelectorShape.h b/libs/widgets/KisVisualColorSelectorShape.h index d65761c109..b0674947d1 100644 --- a/libs/widgets/KisVisualColorSelectorShape.h +++ b/libs/widgets/KisVisualColorSelectorShape.h @@ -1,236 +1,256 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VISUAL_COLOR_SELECTOR_SHAPE_H #define KIS_VISUAL_COLOR_SELECTOR_SHAPE_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisVisualColorSelector.h" #include "KisColorSelectorConfiguration.h" /** * @brief The KisVisualColorSelectorShape class * A 2d widget can represent at maximum 2 coordinates. * So first decide howmany coordinates you need. (onedimensional, or twodimensional) * Then the model, (Channel, HSV, HSL, HSI, YUV). Channel is the raw color channels. * When it finds a non-implemented feature it'll return to Channel. * Then, select the channels you wish to be affected. This uses the model, so for cmyk * the channel is c=0, m=1, y=2, k=3, but for hsv, hue=0, sat=1, and val=2 * These can also be set with 'slotsetactive channels'. * Then finally, connect the displayrenderer, you can also do this with 'setdisplayrenderer' * * Either way, this class is made to be subclassed, with a few virtuals so that the geometry * can be calculated properly. */ class KisVisualColorSelectorShape : public QWidget { Q_OBJECT public: /** * @brief The Dimensions enum * Whether or not the shape is single or two dimensional. **/ enum Dimensions{onedimensional, twodimensional}; enum ColorModel{Channel, HSV, HSL, HSI, HSY, YUV}; explicit KisVisualColorSelectorShape(QWidget *parent, KisVisualColorSelectorShape::Dimensions dimension, KisVisualColorSelectorShape::ColorModel model, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); ~KisVisualColorSelectorShape() override; /** * @brief getCursorPosition * @return current cursor position in shape-coordinates. */ QPointF getCursorPosition(); /** * @brief getDimensions * @return whether this is a single or twodimensional widget. */ Dimensions getDimensions() const; /** * @brief getColorModel * @return the model of this widget. */ ColorModel getColorModel(); /** * @brief getPixmap * @return the pixmap of the gradient, for drawing on with a subclass. * the pixmap will not change unless 'm_d->setPixmap=true' which is toggled by * refresh and update functions. */ bool imagesNeedUpdate() const; QImage getImageMap(); /** * @brief setFullImage * Set the full widget image to be painted. * @param full this should be the full image. */ void setFullImage(QImage full); /** * @brief getCurrentColor * @return the current kocolor */ KoColor getCurrentColor(); /** * @brief setDisplayRenderer * disconnect the old display renderer if needed and connect the new one. * @param displayRenderer */ void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer); /** * @brief getColorFromConverter * @param c a koColor. * @return get the qcolor from the given kocolorusing this widget's display renderer. */ QColor getColorFromConverter(KoColor c); /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ virtual QRect getSpaceForSquare(QRect geom) = 0; virtual QRect getSpaceForCircle(QRect geom) = 0; virtual QRect getSpaceForTriangle(QRect geom) = 0; /** * @brief forceImageUpdate * force the image to recache. */ void forceImageUpdate(); /** * @brief setBorderWidth * set the border of the single dimensional selector. * @param width */ virtual void setBorderWidth(int width) = 0; /** * @brief getChannels * get used channels * @return */ QVector getChannels() const; /** * @brief setHSX * This is for the cursor not to change when selecting * black, white, and desaturated values. Will not change the non-native values. * @param hsx the hsx value. * @param wrangler defines whether this docker will update luminosity if there's not at the least 3\% more variation */ void setHSX(QVector hsx, bool wrangler=false); /** * @brief getHSX sets the sat and hue so they won't * switch around much. * @param hsx the hsx values. * @param wrangler defines whether this docker will update luminosity if there's not at the least 3\% more variation * @return returns hsx, corrected. */ QVector getHSX(QVector hsx, bool wrangler= false); + /** + * @brief setCursorPosition + * Set the cursor to normalized shape coordinates. This will only repaint the cursor. + * @param position normalized shape coordinates ([0,1] range, not yet transformed to actual channel values!) + * @param signal if true, emit a sigCursorMoved signal + */ + void setCursorPosition(QPointF position, bool signal = false); + + /** + * @brief setChannelValues + * Set the current channel values; + * Note that channel values controlled by the shape itself have no effect unless setCursor is true. + * This will trigger a full widget repaint. + * @param position normalized shape coordinates ([0,1] range) + * these are not yet transformed to color space specific ranges! + * @param setCursor if true, sets the cursor too, otherwise the shape-controlled channels are not set + */ + void setChannelValues(QVector4D channelValues, bool setCursor); + Q_SIGNALS: void sigNewColor(KoColor col); void sigHSXchange(); + void sigCursorMoved(QPointF pos); public Q_SLOTS: /** * @brief setColor * Set this widget's current color and change the cursor position. * @param c */ void setColor(KoColor c); /** * @brief setColorFromSibling * set this widget's current color, but don't change the cursor position, * instead sent out a signal of the new color. * @param c */ void setColorFromSibling(KoColor c); /** * @brief slotSetActiveChannels * Change the active channels if necessary. * @param channel1 used by single and twodimensional widgets. * @param channel2 only used by twodimensional widgets. */ void slotSetActiveChannels(int channel1, int channel2); /** * @brief updateFromChangedDisplayRenderer * for updating from the display renderer... not sure why this one is public. */ void updateFromChangedDisplayRenderer(); protected: void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent*) override; private: struct Private; const QScopedPointer m_d; /** * @brief convertShapeCoordinateToWidgetCoordinate * @return take the position in the shape and convert it to screen coordinates. */ virtual QPointF convertShapeCoordinateToWidgetCoordinate(QPointF) const = 0; /** * @brief convertWidgetCoordinateToShapeCoordinate * Convert a coordinate in the widget's height/width to a shape coordinate. * @param coordinate the position your wish to have the shape coordinates of. */ virtual QPointF convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const = 0; /** * @brief updateCursor * Update the cursor position. */ void updateCursor(); QPointF convertKoColorToShapeCoordinate(KoColor c); KoColor convertShapeCoordinateToKoColor(QPointF coordinates, bool cursor = false); /** * @brief getPixmap * @return the pixmap of this shape. */ virtual QRegion getMaskMap() = 0; virtual void drawCursor() = 0; QVector convertvectorqrealTofloat(QVector real); QVector convertvectorfloatToqreal(QVector vloat); }; #endif