File indexing completed on 2024-05-12 16:02:05

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 <QVBoxLayout>
0015 #include <QList>
0016 #include <QtMath>
0017 
0018 #include <KSharedConfig>
0019 #include <KConfigGroup>
0020 
0021 #include "KoColorConversions.h"
0022 #include "KoColorDisplayRendererInterface.h"
0023 #include "KoChannelInfo.h"
0024 #include <KoColorModelStandardIds.h>
0025 #include <QPointer>
0026 
0027 #include "kis_debug.h"
0028 
0029 struct KisVisualColorSelectorShape::Private
0030 {
0031     QImage gradient;
0032     QImage alphaMask;
0033     QImage fullSelector;
0034     bool imagesNeedUpdate { true };
0035     bool alphaNeedsUpdate { true };
0036     bool acceptTabletEvents { false };
0037     QPointF currentCoordinates; // somewhat redundant?
0038     QPointF dragStart;
0039     QVector4D currentChannelValues;
0040     Dimensions dimension;
0041     const KoColorSpace *colorSpace;
0042     int channel1;
0043     int channel2;
0044     const KoColorDisplayRendererInterface *displayRenderer = 0;
0045 };
0046 
0047 KisVisualColorSelectorShape::KisVisualColorSelectorShape(QWidget *parent,
0048                                                          KisVisualColorSelectorShape::Dimensions dimension,
0049                                                          const KoColorSpace *cs,
0050                                                          int channel1,
0051                                                          int channel2,
0052                                                          const KoColorDisplayRendererInterface *displayRenderer): QWidget(parent), m_d(new Private)
0053 {
0054     m_d->dimension = dimension;
0055     m_d->colorSpace = cs;
0056     int maxchannel = m_d->colorSpace->colorChannelCount()-1;
0057     m_d->channel1 = qBound(0, channel1, maxchannel);
0058     m_d->channel2 = qBound(0, channel2, maxchannel);
0059     this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0060     setDisplayRenderer(displayRenderer);
0061 }
0062 
0063 KisVisualColorSelectorShape::~KisVisualColorSelectorShape()
0064 {
0065 }
0066 
0067 QPointF KisVisualColorSelectorShape::getCursorPosition() {
0068     return m_d->currentCoordinates;
0069 }
0070 
0071 void KisVisualColorSelectorShape::setCursorPosition(QPointF position, bool signal)
0072 {
0073     QPointF newPos(qBound(0.0, position.x(), 1.0), qBound(0.0, position.y(), 1.0));
0074     if (newPos != m_d->currentCoordinates)
0075     {
0076         m_d->currentCoordinates = newPos;
0077         // for internal consistency, because we have a bit of redundancy here
0078         m_d->currentChannelValues[m_d->channel1] = newPos.x();
0079         if (m_d->dimension == Dimensions::twodimensional){
0080             m_d->currentChannelValues[m_d->channel2] = newPos.y();
0081         }
0082         update();
0083         if (signal){
0084             emit sigCursorMoved(newPos);
0085         }
0086     }
0087 }
0088 
0089 void KisVisualColorSelectorShape::setChannelValues(QVector4D channelValues, bool setCursor)
0090 {
0091     //qDebug() << this  << "setChannelValues";
0092     m_d->currentChannelValues = channelValues;
0093     if (setCursor) {
0094         m_d->currentCoordinates = QPointF(qBound(0.f, channelValues[m_d->channel1], 1.f),
0095                                           qBound(0.f, channelValues[m_d->channel2], 1.f));
0096     }
0097     else {
0098         // for internal consistency, because we have a bit of redundancy here
0099         m_d->currentChannelValues[m_d->channel1] = m_d->currentCoordinates.x();
0100         if (m_d->dimension == Dimensions::twodimensional){
0101             m_d->currentChannelValues[m_d->channel2] = m_d->currentCoordinates.y();
0102         }
0103     }
0104     m_d->imagesNeedUpdate = true;
0105     update();
0106 }
0107 
0108 void KisVisualColorSelectorShape::setAcceptTabletEvents(bool on)
0109 {
0110     m_d->acceptTabletEvents = on;
0111 }
0112 
0113 void KisVisualColorSelectorShape::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer)
0114 {
0115     if (displayRenderer) {
0116         m_d->displayRenderer = displayRenderer;
0117     } else {
0118         m_d->displayRenderer = KoDumbColorDisplayRenderer::instance();
0119     }
0120 }
0121 
0122 void KisVisualColorSelectorShape::forceImageUpdate()
0123 {
0124     //qDebug() << this  << "forceImageUpdate";
0125     m_d->alphaNeedsUpdate = true;
0126     m_d->imagesNeedUpdate = true;
0127 }
0128 
0129 QColor KisVisualColorSelectorShape::getColorFromConverter(KoColor c){
0130     QColor col;
0131     KoColor color = c;
0132     if (m_d->displayRenderer) {
0133         color.convertTo(m_d->displayRenderer->getPaintingColorSpace());
0134         col = m_d->displayRenderer->toQColor(c);
0135     } else {
0136         col = c.toQColor();
0137     }
0138     return col;
0139 }
0140 
0141 // currently unused?
0142 void KisVisualColorSelectorShape::slotSetActiveChannels(int channel1, int channel2)
0143 {
0144     //qDebug() << this  << "slotSetActiveChannels";
0145     int maxchannel = m_d->colorSpace->colorChannelCount()-1;
0146     m_d->channel1 = qBound(0, channel1, maxchannel);
0147     m_d->channel2 = qBound(0, channel2, maxchannel);
0148     m_d->imagesNeedUpdate = true;
0149     update();
0150 }
0151 
0152 bool KisVisualColorSelectorShape::imagesNeedUpdate() const {
0153     return m_d->imagesNeedUpdate;
0154 }
0155 
0156 QImage KisVisualColorSelectorShape::getImageMap()
0157 {
0158     //qDebug() << this  << ">>>>>>>>> getImageMap()" << m_d->imagesNeedUpdate;
0159 
0160     if (m_d->imagesNeedUpdate) {
0161         // Fill a buffer with the right kocolors
0162         m_d->gradient = renderBackground(m_d->currentChannelValues, m_d->colorSpace->pixelSize());
0163         m_d->imagesNeedUpdate = false;
0164     }
0165     return m_d->gradient;
0166 }
0167 
0168 const QImage KisVisualColorSelectorShape::getAlphaMask() const
0169 {
0170     if (m_d->alphaNeedsUpdate) {
0171         m_d->alphaMask = renderAlphaMask();
0172         m_d->alphaNeedsUpdate = false;
0173     }
0174     return m_d->alphaMask;
0175 }
0176 
0177 QImage KisVisualColorSelectorShape::convertImageMap(const quint8 *rawColor, quint32 bufferSize, QSize imgSize) const
0178 {
0179     Q_ASSERT(bufferSize == imgSize.width() * imgSize.height() * m_d->colorSpace->pixelSize());
0180     QImage image;
0181     // Convert the buffer to a qimage
0182     if (m_d->displayRenderer) {
0183         image = m_d->displayRenderer->convertToQImage(m_d->colorSpace, rawColor, imgSize.width(), imgSize.height());
0184     }
0185     else {
0186         image = m_d->colorSpace->convertToQImage(rawColor, imgSize.width(), imgSize.height(), 0,
0187                                                  KoColorConversionTransformation::internalRenderingIntent(),
0188                                                  KoColorConversionTransformation::internalConversionFlags());
0189     }
0190     // safeguard:
0191     if (image.isNull())
0192     {
0193         image = QImage(width(), height(), QImage::Format_ARGB32);
0194         image.fill(Qt::black);
0195     }
0196 
0197     return image;
0198 }
0199 
0200 QImage KisVisualColorSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const
0201 {
0202     const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent());
0203     Q_ASSERT(selector);
0204 
0205     // Hi-DPI aware rendering requires that we determine the device pixel dimension;
0206     // actual widget size in device pixels is not accessible unfortunately, it might be 1px smaller...
0207     const qreal deviceDivider = 1.0 / devicePixelRatioF();
0208     const int deviceWidth = qCeil(width() * devicePixelRatioF());
0209     const int deviceHeight = qCeil(height() * devicePixelRatioF());
0210     quint32 imageSize = deviceWidth * deviceHeight * m_d->colorSpace->pixelSize();
0211     QScopedArrayPointer<quint8> raw(new quint8[imageSize] {});
0212     quint8 *dataPtr = raw.data();
0213     QVector4D coordinates = channelValues;
0214 
0215     QImage alpha = getAlphaMask();
0216     bool checkAlpha = !alpha.isNull() && alpha.valid(deviceWidth - 1, deviceHeight - 1);
0217     KIS_SAFE_ASSERT_RECOVER(!checkAlpha || alpha.format() == QImage::Format_Alpha8) {
0218         checkAlpha = false;
0219     }
0220 
0221     KoColor filler(Qt::white, m_d->colorSpace);
0222     for (int y = 0; y < deviceHeight; y++) {
0223         const uchar *alphaLine = checkAlpha ? alpha.scanLine(y) : 0;
0224         for (int x=0; x < deviceWidth; x++) {
0225             if (!checkAlpha || alphaLine[x]) {
0226                 QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPointF(x, y) * deviceDivider);
0227                 coordinates[m_d->channel1] = newcoordinate.x();
0228                 if (m_d->dimension == Dimensions::twodimensional) {
0229                     coordinates[m_d->channel2] = newcoordinate.y();
0230                 }
0231                 KoColor c = selector->convertShapeCoordsToKoColor(coordinates);
0232                 memcpy(dataPtr, c.data(), pixelSize);
0233             }
0234             else {
0235                 // need to write a color with non-zero alpha, otherwise the display converter
0236                 // will for some arcane reason crop the final QImage and screw rendering
0237                 memcpy(dataPtr, filler.data(), pixelSize);
0238             }
0239             dataPtr += pixelSize;
0240         }
0241     }
0242     QImage image = convertImageMap(raw.data(), imageSize, QSize(deviceWidth, deviceHeight));
0243     image.setDevicePixelRatio(devicePixelRatioF());
0244 
0245     if (!alpha.isNull()) {
0246         QPainter painter(&image);
0247         // transfer alphaMask to Alpha channel
0248         painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0249         painter.drawImage(0, 0, alpha);
0250     }
0251 
0252     return image;
0253 }
0254 
0255 QImage KisVisualColorSelectorShape::renderAlphaMask() const
0256 {
0257     return QImage();
0258 }
0259 
0260 QPointF KisVisualColorSelectorShape::mousePositionToShapeCoordinate(const QPointF &pos, const QPointF &dragStart) const
0261 {
0262     Q_UNUSED(dragStart);
0263     return convertWidgetCoordinateToShapeCoordinate(pos);
0264 }
0265 
0266 void KisVisualColorSelectorShape::mousePressEvent(QMouseEvent *e)
0267 {
0268     if (e->button() == Qt::LeftButton) {
0269         m_d->dragStart = e->localPos();
0270         QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
0271         setCursorPosition(coordinates, true);
0272     }
0273     else {
0274         e->ignore();
0275     }
0276 }
0277 
0278 void KisVisualColorSelectorShape::mouseMoveEvent(QMouseEvent *e)
0279 {
0280     if (e->buttons() & Qt::LeftButton) {
0281         QPointF coordinates = mousePositionToShapeCoordinate(e->localPos(), m_d->dragStart);
0282         setCursorPosition(coordinates, true);
0283     } else {
0284         e->ignore();
0285     }
0286 }
0287 
0288 void KisVisualColorSelectorShape::mouseReleaseEvent(QMouseEvent *e)
0289 {
0290     if (e->button() != Qt::LeftButton) {
0291         e->ignore();
0292     }
0293 }
0294 
0295 void KisVisualColorSelectorShape::tabletEvent(QTabletEvent* event)
0296 {
0297     // only accept tablet events that are associated to "left" button
0298     // NOTE: QTabletEvent does not have a windowPos() equivalent, but we don't need it
0299     if (m_d->acceptTabletEvents &&
0300         (event->button() == Qt::LeftButton || (event->buttons() & Qt::LeftButton)))
0301     {
0302         event->accept();
0303         switch (event->type()) {
0304         case  QEvent::TabletPress: {
0305             QMouseEvent mouseEvent(QEvent::MouseButtonPress, event->posF(), event->posF(),
0306                                    event->globalPosF(), event->button(), event->buttons(),
0307                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0308             mousePressEvent(&mouseEvent);
0309             break;
0310         }
0311         case QEvent::TabletMove: {
0312             QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), event->posF(),
0313                                    event->globalPosF(), event->button(), event->buttons(),
0314                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0315             mouseMoveEvent(&mouseEvent);
0316             break;
0317         }
0318         case QEvent::TabletRelease: {
0319             QMouseEvent mouseEvent(QEvent::MouseButtonRelease, event->posF(), event->posF(),
0320                                    event->globalPosF(), event->button(), event->buttons(),
0321                                    event->modifiers(), Qt::MouseEventSynthesizedByApplication);
0322             mouseReleaseEvent(&mouseEvent);
0323             break;
0324         }
0325         default:
0326             event->ignore();
0327         }
0328     }
0329 }
0330 
0331 void KisVisualColorSelectorShape::paintEvent(QPaintEvent*)
0332 {
0333     QPainter painter(this);
0334 
0335     drawCursor();
0336     painter.drawImage(0,0,m_d->fullSelector);
0337 }
0338 
0339 void KisVisualColorSelectorShape::resizeEvent(QResizeEvent *)
0340 {
0341     forceImageUpdate();
0342     setMask(getMaskMap());
0343 }
0344 
0345 KisVisualColorSelectorShape::Dimensions KisVisualColorSelectorShape::getDimensions() const
0346 {
0347     return m_d->dimension;
0348 }
0349 
0350 void KisVisualColorSelectorShape::setFullImage(QImage full)
0351 {
0352     m_d->fullSelector = full;
0353 }
0354 KoColor KisVisualColorSelectorShape::getCurrentColor()
0355 {
0356     const KisVisualColorSelector *selector = qobject_cast<KisVisualColorSelector*>(parent());
0357     if (selector)
0358     {
0359         return selector->convertShapeCoordsToKoColor(m_d->currentChannelValues);
0360     }
0361     return KoColor(m_d->colorSpace);
0362 }
0363 
0364 QVector <int> KisVisualColorSelectorShape::getChannels() const
0365 {
0366     QVector <int> channels(2);
0367     channels[0] = m_d->channel1;
0368     channels[1] = m_d->channel2;
0369     return channels;
0370 }