File indexing completed on 2024-05-19 04:29:29

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "KisVisualColorSelectorShape.h"
0007 
0008 #include <QColor>
0009 #include <QImage>
0010 #include <QPainter>
0011 #include <QRect>
0012 #include <QVector>
0013 #include <QVector4D>
0014 #include <QList>
0015 #include <QtMath>
0016 
0017 #include "KoColorConversions.h"
0018 #include "KoColorSpace.h"
0019 #include "KoColorDisplayRendererInterface.h"
0020 #include "KoChannelInfo.h"
0021 #include <KoColorModelStandardIds.h>
0022 #include <QPointer>
0023 
0024 #include "kis_debug.h"
0025 
0026 struct KisVisualColorSelectorShape::Private
0027 {
0028     QImage gradient;
0029     QImage alphaMask;
0030     QImage staticBackground;
0031     bool imagesNeedUpdate { true };
0032     bool alphaNeedsUpdate { true };
0033     bool acceptTabletEvents { false };
0034     QPointF currentCoordinates; // somewhat redundant?
0035     QPointF dragStart;
0036     QVector4D currentChannelValues;
0037     Dimensions dimension;
0038     int channel1;
0039     int channel2;
0040     quint32 channelMask;
0041 };
0042 
0043 KisVisualColorSelectorShape::KisVisualColorSelectorShape(KisVisualColorSelector *parent,
0044                                                          KisVisualColorSelectorShape::Dimensions dimension,
0045                                                          int channel1,
0046                                                          int channel2): QWidget(parent), m_d(new Private)
0047 {
0048     m_d->dimension = dimension;
0049     int maxchannel = parent->selectorModel()->colorSpace()->colorChannelCount()-1;
0050     m_d->channel1 = qBound(0, channel1, maxchannel);
0051     m_d->channel2 = qBound(0, channel2, maxchannel);
0052     m_d->channelMask = 1 << channel1;
0053     if (dimension == Dimensions::twodimensional) {
0054         m_d->channelMask |= 1 << channel2;
0055     }
0056     this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0057 }
0058 
0059 KisVisualColorSelectorShape::~KisVisualColorSelectorShape()
0060 {
0061 }
0062 
0063 QPointF KisVisualColorSelectorShape::getCursorPosition() const {
0064     return m_d->currentCoordinates;
0065 }
0066 
0067 void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
0068 {
0069     QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
0070     if (newPos != m_d->currentCoordinates)
0071     {
0072         m_d->currentCoordinates = newPos;
0073         // for internal consistency, because we have a bit of redundancy here
0074         m_d->currentChannelValues[m_d->channel1] = newPos.x();
0075         if (m_d->dimension == Dimensions::twodimensional){
0076             m_d->currentChannelValues[m_d->channel2] = newPos.y();
0077         }
0078         update();
0079         if (signal){
0080             emit sigCursorMoved(newPos);
0081         }
0082     }
0083 }
0084 
0085 void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, quint32 channelFlags)
0086 {
0087     //qDebug() << this  << "setChannelValues";
0088     m_d->currentChannelValues = channelValues;
0089     bool setCursor = channelFlags & m_d->channelMask;
0090     if (setCursor) {
0091         m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
0092                                           qBound(0.f, channelValues[m_d->channel2], 1.f));
0093     }
0094     else {
0095         // for internal consistency, because we have a bit of redundancy here
0096         m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
0097         if (m_d->dimension == Dimensions::twodimensional){
0098             m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
0099         }
0100     }
0101     m_d->imagesNeedUpdate = m_d->imagesNeedUpdate || channelFlags & ~m_d->channelMask;
0102     update();
0103 }
0104 
0105 void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on)
0106 {
0107     m_d->acceptTabletEvents = on;
0108 }
0109 
0110 bool KisVisualColorSelectorShape::isHueControl() const
0111 {
0112     return selectorModel()->isHSXModel()
0113             && getDimensions() == KisVisualColorSelectorShape::onedimensional
0114             && m_d->channel1 == 0;
0115 }
0116 
0117 bool KisVisualColorSelectorShape::supportsGamutMask() const
0118 {
0119     return false;
0120 }
0121 
0122 void KisVisualColorSelectorShape::forceImageUpdate()
0123 {
0124     //qDebug() << this  << "forceImageUpdate";
0125     m_d->alphaNeedsUpdate = true;
0126     m_d->imagesNeedUpdate = true;
0127 }
0128 
0129 void KisVisualColorSelectorShape::updateGamutMask()
0130 {
0131     // Nothing to do if gamut masks not supported
0132 }
0133 
0134 QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c)
0135 {
0136     const KoColorDisplayRendererInterface *renderer = colorSelector()->displayRenderer();
0137     return renderer->toQColor(c, colorSelector()->proofColors());
0138 }
0139 
0140 KisVisualColorSelector *KisVisualColorSelectorShape::colorSelector() const
0141 {
0142     KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
0143     KIS_ASSERT(selectorWidget);
0144     return selectorWidget;
0145 }
0146 
0147 KisVisualColorModel *KisVisualColorSelectorShape::selectorModel() const
0148 {
0149     KisVisualColorSelector* selectorWidget = qobject_cast<KisVisualColorSelector*>(parent());
0150     KIS_ASSERT(selectorWidget);
0151     return selectorWidget->selectorModel().data();
0152 }
0153 
0154 const QImage& KisVisualColorSelectorShape::getImageMap()
0155 {
0156     //qDebug() << this  << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
0157 
0158     if (m_d->imagesNeedUpdate) {
0159         // NOTE: pure static backgrounds are currently somewhat implicitly handled,
0160         // it would be nicer to avoid re-checking and overwriting m_d->gradient.
0161         // But QImage's implicit data sharing allows all this mindless by-value stuff...
0162         m_d->gradient = compositeBackground();
0163         m_d->imagesNeedUpdate = false;
0164     }
0165     return m_d->gradient;
0166 }
0167 
0168 QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
0169 {
0170     const KoColorSpace *colorSpace = selectorModel()->colorSpace();
0171     Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * colorSpace->pixelSize());
0172     const KoColorDisplayRendererInterface *renderer = colorSelector()->displayRenderer();
0173 
0174     // Convert the buffer to a qimage
0175     QImage image = renderer->toQImage(colorSpace, rawColor, imgSize, colorSelector()->proofColors());
0176 
0177     // safeguard:
0178     if (image.isNull())
0179     {
0180         image = QImage(width(), height(), QImage::Format_ARGB32);
0181         image.fill(Qt::black);
0182     }
0183 
0184     return image;
0185 }
0186 
0187 QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, const QImage &alpha) const
0188 {
0189     const KisVisualColorModel *selector = selectorModel();
0190     Q_ASSERT(selector);
0191 
0192     // Hi-DPI aware rendering requires that we determine the device pixel dimension;
0193     // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
0194     const qreal deviceDivider = 1.0 / devicePixelRatioF();
0195     const int deviceWidth = qCeil(width() * devicePixelRatioF());
0196     const int deviceHeight = qCeil(height() * devicePixelRatioF());
0197     quint32 imageSize = deviceWidth * deviceHeight * selector->colorSpace()->pixelSize();
0198     QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
0199     quint8 *dataPtr = raw.data();
0200     QVector4D coordinates = channelValues;
0201     const qsizetype pixelSize = selector->colorSpace()->pixelSize();
0202 
0203     bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
0204     KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
0205         checkAlpha = false;
0206     }
0207 
0208     KoColor filler(Qt::white, selector->colorSpace());
0209     for (int y = 0; y < deviceHeight; y++) {
0210         const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
0211         for (int x=0; x < deviceWidth; x++) {
0212             if (!checkAlpha || alphaLine[x]) {
0213                 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
0214                 coordinates[m_d->channel1] = newcoordinate.x();
0215                 if (m_d->dimension == Dimensions::twodimensional) {
0216                     coordinates[m_d->channel2] = newcoordinate.y();
0217                 }
0218                 KoColor c = selector->convertChannelValuesToKoColor(coordinates);
0219                 memcpy(dataPtr, c.data(), pixelSize);
0220             }
0221             else {
0222                 // need to write a color with non-zero alpha, otherwise the display converter
0223                 // will for some arcane reason crop the final QImage and screw rendering
0224                 memcpy(dataPtr, filler.data(), pixelSize);
0225             }
0226             dataPtr += pixelSize;
0227         }
0228     }
0229     QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
0230     image.setDevicePixelRatio(devicePixelRatioF());
0231 
0232     if (!alpha.isNull()) {
0233         QPainter painter(&image);
0234         // transfer alphaMask to Alpha channel
0235         painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0236         painter.drawImage(0, 0, alpha);
0237     }
0238 
0239     return image;
0240 }
0241 
0242 QImage KisVisualColorSelectorShape::compositeBackground() const
0243 {
0244     // Shapes are expect to return a valid alpha mask or a valid
0245     // static alpha mask. If they provide both, the rendered backgrounds
0246     // get composited.
0247     if (m_d->alphaNeedsUpdate) {
0248         QImage staticAlpha = renderStaticAlphaMask();
0249         if (!staticAlpha.isNull()) {
0250             QVector4D neutralValues(1, 1, 1, 1);
0251             switch (selectorModel()->colorModel()) {
0252             case KisVisualColorModel::HSL:
0253             case KisVisualColorModel::HSI:
0254             case KisVisualColorModel::HSY:
0255                 neutralValues.setZ(0.5f);
0256             default:
0257                 break;
0258             }
0259 
0260             m_d->staticBackground = renderBackground(neutralValues, staticAlpha);
0261         }
0262         m_d->alphaMask = renderAlphaMask();
0263         m_d->alphaNeedsUpdate = false;
0264     }
0265     if (m_d->alphaMask.isNull()) {
0266         return m_d->staticBackground;
0267     }
0268 
0269     QImage bgImage = renderBackground(m_d->currentChannelValues, m_d->alphaMask);
0270     if (!m_d->staticBackground.isNull()) {
0271         QPainter painter(&bgImage);
0272         // composite static and dynamic background parts
0273         painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
0274         painter.drawImage(0, 0, m_d->staticBackground);
0275     }
0276     return bgImage;
0277 }
0278 
0279 QImage KisVisualColorSelectorShape::renderAlphaMask() const
0280 {
0281     return QImage();
0282 }
0283 
0284 QImage KisVisualColorSelectorShape::renderStaticAlphaMask() const
0285 {
0286     return QImage();
0287 }
0288 
0289 QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
0290 {
0291     Q_UNUSED(dragStart);
0292     return convertWidgetCoordinateToShapeCoordinate(pos);
0293 }
0294 
0295 void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e)
0296 {
0297     if (e->button() == Qt::LeftButton) {
0298         m_d->dragStart = e->localPos();
0299         emit colorSelector()->sigInteraction(true);
0300         QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
0301         setCursorPosition(coordinates, true);
0302     }
0303     else {
0304         e->ignore();
0305     }
0306 }
0307 
0308 void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e)
0309 {
0310     if (e->buttons() & Qt::LeftButton) {
0311         QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
0312         setCursorPosition(coordinates, true);
0313     } else {
0314         e->ignore();
0315     }
0316 }
0317 
0318 void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e)
0319 {
0320     if (e->button() == Qt::LeftButton) {
0321         emit colorSelector()->sigInteraction(false);
0322     } else {
0323         e->ignore();
0324     }
0325 }
0326 
0327 void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event)
0328 {
0329     // only accept tablet events that are associated to "left" button
0330     // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
0331     if (m_d->acceptTabletEvents &&
0332         (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
0333     {
0334         event->accept();
0335         switch (event->type()) {
0336         case  QEvent::TabletPress: {
0337             QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
0338                                    event->globalPosF(), event->button(), event->buttons(),
0339                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0340             mousePressEvent(&mouseEvent);
0341             break;
0342         }
0343         case QEvent::TabletMove: {
0344             QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
0345                                    event->globalPosF(), event->button(), event->buttons(),
0346                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0347             mouseMoveEvent(&mouseEvent);
0348             break;
0349         }
0350         case QEvent::TabletRelease: {
0351             QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
0352                                    event->globalPosF(), event->button(), event->buttons(),
0353                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0354             mouseReleaseEvent(&mouseEvent);
0355             break;
0356         }
0357         default:
0358             event->ignore();
0359         }
0360     }
0361 }
0362 
0363 void KisVisualColorSelectorShape::paintEvent(QPaintEvent*)
0364 {
0365     QPainter painter(this);
0366 
0367     const QImage &fullSelector = getImageMap();
0368     if (!fullSelector.isNull()) {
0369         painter.drawImage(0, 0, fullSelector);
0370     }
0371 
0372     drawGamutMask(painter);
0373 
0374     if (isEnabled()) {
0375         painter.setRenderHint(QPainter::Antialiasing);
0376         drawCursor(painter);
0377     }
0378 }
0379 
0380 void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *)
0381 {
0382     forceImageUpdate();
0383     updateGamutMask();
0384     setMask(getMaskMap());
0385 }
0386 
0387 void KisVisualColorSelectorShape::drawGamutMask(QPainter &painter)
0388 {
0389     // Nothing to do if gamut masks not supported
0390     Q_UNUSED(painter);
0391 }
0392 
0393 KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const
0394 {
0395     return m_d->dimension;
0396 }
0397 
0398 KoColor KisVisualColorSelectorShape::getCurrentColor()
0399 {
0400     const KisVisualColorModel *selector = selectorModel();
0401     if (selector)
0402     {
0403         return selector->convertChannelValuesToKoColor(m_d->currentChannelValues);
0404     }
0405     return KoColor();
0406 }
0407 
0408 int KisVisualColorSelectorShape::channel(int dimension) const
0409 {
0410     if (dimension == 0) {
0411         return m_d->channel1;
0412     }
0413     if (dimension == 1 && getDimensions() == twodimensional) {
0414         return m_d->channel2;
0415     }
0416     return -1;
0417 }
0418 
0419 quint32 KisVisualColorSelectorShape::channelMask() const
0420 {
0421     return m_d->channelMask;
0422 }