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 }